javaFx 安全开发 三

简介: javaFx 安全开发

列表

ListView   列表
TreeView   树列表
TableView  表格
TreeTableView  多列多表

代码分析:

首先就是使用了 hbox 作为top部分的 文件行 和 三个按钮

每个按钮对应一个监听器 监听不同的处理动作

整体使用了布局嵌套 使用了student泛型 把内容显示在列表里

列表里需要自定义单元格 最终把字符显示在单元格里

package sample;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.Callback;
public class DemoList extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        // 添加hbox
        HBox hBox = new HBox();
        TextField field = new TextField();
        Button b1 = new Button("添加");
        Button b2 = new Button("删除");
        Button b3 = new Button("修改");
        hBox.getChildren().addAll(field,b1,b2,b3);
        // 设置listview
        ListView<Student> listView = new ListView<>();
        // 设置数据源
        ObservableList<Student> list = FXCollections.observableArrayList();
        // 设置数据源
        listView.setItems(list);
        // 生成单元格
        listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>() {
            @Override
            public ListCell<Student> call(ListView<Student> param) {
                return new mycell();
            }
        });
        // 添加数据
        list.add(new Student("张三"));
        list.add(new Student("王五"));
        list.add(new Student("李丹"));
        BorderPane root = new BorderPane();
        HBox.setHgrow(field, Priority.ALWAYS);
        // 添加按钮监听
        b1.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                String text = field.getText();
                list.add(new Student(text));
            }
        });
        b2.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                // 选择当前选择的行
                int i = listView.getSelectionModel().getSelectedIndex();
                if (i >= 0){
                    list.remove(i);
                }
            }
        });
        b3.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                int i = listView.getSelectionModel().getSelectedIndex();
                String name = field.getText();
                if (i > 0){
                    // 获取索引对应的数字
                    Student s = list.get(i);
                    // 覆盖数据
                    s.name = name;
                    list.set(i,s);
                }
            }
        });
        root.setTop(hBox);
        root.setCenter(listView);
        primaryStage.setTitle("列表属性");
        primaryStage.setScene(new Scene(root,400,300));
        primaryStage.show();
    }
    // 单元格显示
    static class mycell extends ListCell<Student>{
        @Override
        protected void updateItem(Student item, boolean empty) {
            super.updateItem(item, empty);
            if (item != null){
                this.setText(item.name);
            } else if(empty || item == null){
                setText(null);
            }
        }
    }
    static class Student{
        private String name;
        public Student(){}
        public Student( String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
}

效果图如下

鼠标事件处理

鼠标可以常用的监听方式
左键单击
左键双击
右键单击
在MouseEvent 对象里,能得到以下信息:
event.getButton()  按钮 (左,中,右)
event.getClickCount()  移动0 单击1 双击2
event.getX() 点击位置 窗口坐标
event.getSceneX()  单击位置 屏幕坐标
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class demo1 extends Application{
    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane root = new BorderPane();
        Button b1 = new Button("点我");
        b1.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                // 对鼠标点击的事件进行处理
                if ( event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2){
                    System.out.println("鼠标左键点击了两次");
                } else if(event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1){
                    System.out.println("鼠标左键点击了一次");
                }
                /**
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("弹窗标题");
                alert.setHeaderText("弹窗信息");
                alert.showAndWait();
                */
            }
        });
        root.setCenter(b1);
        primaryStage.setTitle("鼠标操作");
        primaryStage.setScene(new Scene(root,500,50));
        primaryStage.show();
    }
}

640.png

树列表

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class DemoTreeView extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception{
        StackPane root = new StackPane();
        TreeItem treeItem = new TreeItem<>("根目录");
        treeItem.setExpanded(true);
        for(int i = 0;i < 5;i++){
            TreeItem item = new TreeItem<>("节点:" + i);
            treeItem.getChildren().add(item);
        }
        TreeView treeView = new TreeView<>(treeItem);
        root.getChildren().add(treeView);
        primaryStage.setTitle("TreeView的使用");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }
}

代码分析:

使用treeview 展示树列表 多层嵌套TreeItem 就是多层树列表

多列表

顾名思义 就是树列表 多排几列 如下图: 名词对应的列 修改时间 类型 大小等

640.png

import java.io.File;
public class UploadItem{
    public long localId; // 本地上传分配的ID
    public String localGUID; // 本地上传分配的GUID
    public String thumb;
    public File filePath; // 本地文件路径
    public String title;  // 标题, 默认为文件名, 但允许被用户改名
    public long size;  // 文件大小
    public long timeCreated;
    public UploadItem(){}
    public UploadItem(String title, long size, long timeCreated){
        this.title = title;
        this.size = size;
        this.timeCreated = timeCreated;
    }
}
package sample.duoTree;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.BorderPane;
public class Main extends Application{
    TreeTableView<UploadItem> treeTable = new TreeTableView<UploadItem>();
    @Override
    public void start(Stage primaryStage){
        initTreeTable();
        initTreeData();
        BorderPane root = new BorderPane();
        root.setCenter(treeTable);
        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    private void initTreeTable()
{
        // 添加多个列
        TreeTableColumn<UploadItem, UploadItem> columns[] = new TreeTableColumn[3];
        columns[0] = new TreeTableColumn("文件名");
        columns[1] = new TreeTableColumn("大小");
        columns[2] = new TreeTableColumn("任务创建时间");
        treeTable.getColumns().addAll(columns);
        // 定义每个列的列宽
        columns[0].setPrefWidth(150);
        columns[1].setPrefWidth(80);
        columns[2].setPrefWidth(120);
        // 设置 CellValueFactory (此段写法固定)
        Callback cellValueFactory = new Callback() {
            @Override
            public Object call(Object param){
                CellDataFeatures p = (CellDataFeatures)param;
                return p.getValue().valueProperty();
            }
        };
        for(int i=0; i<columns.length; i++){
            columns[i].setCellValueFactory(cellValueFactory);
        }
        // 设置CellFactory,定义每一列的单元格的显示
        // 这里使用了lambda表达式,否则写起来太长了!
        columns[0].setCellFactory((param)->{
            return new MyTableTreeCell("title");
        });
        columns[1].setCellFactory((param)->{
            return new MyTableTreeCell("size");
        });
        columns[2].setCellFactory((param)->{
            return new MyTableTreeCell("filePath");
        });
    }
    private void initTreeData(){
        // 根节点只是占一个位置
        TreeItem<UploadItem> root = new TreeItem<UploadItem>(new UploadItem());
        treeTable.setRoot( root );
        treeTable.setShowRoot(false);
        UploadItem data_1 = new UploadItem("测试视频", 192000, System.currentTimeMillis());
        root.getChildren().add( new TreeItem( data_1));
        UploadItem data_2 = new UploadItem("测试图片", 239000, System.currentTimeMillis());
        root.getChildren().add( new TreeItem( data_2));
    }
    // 单元格的显示
    class MyTableTreeCell extends TreeTableCell<UploadItem,UploadItem>{
        String columnID;
        public MyTableTreeCell(String columnID){
            this.columnID = columnID;
        }
        @Override
        protected void updateItem(UploadItem item, boolean empty){
            super.updateItem(item, empty);
            // 自定义单元格显示内容
            if (empty || item == null){
                setText(null);
                setGraphic(null);
            } else{
                setGraphic(null);
                if(columnID.equals("title"))
                    this.setText(String.valueOf(item.title));
                else if(columnID.equals("size"))
                    this.setText(String.valueOf(item.size));
                else if(columnID.equals("filePath"))
                    this.setText(String.valueOf(item.timeCreated));
            }
        }
    }
}


案例 文件查看器

思路:

程序支持查看java代码 txt文件 图片。

左侧显示文件名 右侧显示其内容

package sample.readUtils;
import java.io.File;
public class FileItem {
    public String fileName;   // 文件名
    public String firstName;  // 前缀名
    public File file;
    public int type = BAD_FORMAT; // 1, 文本文件; 2,图片文件; -1, 不支持的文件类型
    // 文件类型常量
    public static final int TEXT = 1;
    public static final int IMAGE = 2;
    public static final int BAD_FORMAT = -1;
    // 后缀数组
    private final String[] txtTypes = { "txt", "java"};
    private final String[] imageTypes = { "jpg", "jpeg", "png", "bmp" };
    // 构造函数,传入一个file并取得file的名字和类型
    public FileItem(File file) {
        this.file = file;
        // 取得文件名
        fileName = file.getName();
        firstName = getFileFirstName(fileName);
        // 根据文件后缀来判断文件的类型
        String suffix = getFileSuffix(fileName);
        type = BAD_FORMAT;
        if (contains(txtTypes, suffix))
            type = TEXT;
        else if (contains(imageTypes, suffix))
            type = IMAGE;
    }
    // 判断是否图片
    public boolean contains(String[] types, String suffix) {
        suffix = suffix.toLowerCase(); // 统一转成小写
        for (String s : types) {
            if (s.equals(suffix))
                return true;
        }
        return false;
    }
    // 获取文件名的后缀
    public String getFileSuffix(String name) {
        int pos = name.lastIndexOf('.');
        if (pos > 0)
            return name.substring(pos + 1);
        return ""; // 无后缀文件
    }
    // 获取文件名前缀
    public String getFileFirstName(String name) {
        int pos = name.lastIndexOf('.');
        if(pos > 0) {
            return name.substring(0, pos);
        }
        return "";
    }
}
package sample.readUtils;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ListCell;
/*
 * 左侧的文件列表
 */
import javafx.scene.control.ListView;
import javafx.util.Callback;
public class FileListView extends ListView<FileItem> {
    private ObservableList<FileItem> listData = FXCollections.observableArrayList();
    // 构造函数
    public FileListView() {
        setItems(listData);
        // 设置单元格生成器 (工厂)
        setCellFactory(new Callback<ListView<FileItem>, ListCell<FileItem>>() {
            @Override
            public ListCell<FileItem> call(ListView<FileItem> param)
{
                return new MyListCell();
            }
        });
    }
    // 获取数据源
    public ObservableList<FileItem> data() {
        return listData;
    }
    // 设置单元格显示
    static class MyListCell extends ListCell<FileItem> {
        @Override
        protected void updateItem(FileItem item, boolean empty) {
            // FX框架要求必须先调用 super.updateItem()
            super.updateItem(item, empty);
            // 自己的代码
            if (item == null) {
                this.setText("");
            } else {
                this.setText(item.fileName);
            }
        }
    }
}
package sample.readUtils;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
/*
 * 用于显示图片的工具类
 * 封装成一个容器
 * 这个容器显示整张图片
 * 适应父窗口
 */
public class MyImagePane extends Pane {
    ImageView imageView = new ImageView();
    Image image;
    public MyImagePane() {
        // 添加图片
        getChildren().add(imageView);
    }
    public void showImage(Image image) {
        this.image = image;
        imageView.setImage(image);
        layout();
    }
    @Override
    protected void layoutChildren() {
        double w = getWidth();
        double h = getHeight();
        // 对ImageView进行摆放,使其适应父窗口
        imageView.resizeRelocate(0, 0, w, h);
        imageView.setFitWidth(w);
        imageView.setFitHeight(h);
        imageView.setPreserveRatio(true);
    }
}
package sample.readUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class TextFileUtils {
    public static String read(File f, String charset) throws Exception{
        FileInputStream fstream = new FileInputStream(f);
        try {
            int fileSize = (int)f.length();
            if(fileSize > 1024*512)
                throw new Exception("File too large to read! size=" + fileSize);
            byte[] buffer = new byte[fileSize];
            // 读取到字符数组里
            fstream.read(buffer);
            return new String(buffer, charset);
        }finally {
            try{
                fstream.close();
            }catch(Exception e) {}
        }
    }
    public static void write(File f, String text, String charset) throws Exception
{
        FileOutputStream fstream = new FileOutputStream(f);
        try{
            fstream.write( text.getBytes( charset ));
        }finally
        {
            fstream.close();
        }
    }
}
package sample.readUtils;
import java.io.File;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
    FileListView fileList = new FileListView();
    TabPane tabPane = new TabPane();
    @Override
    public void start(Stage primaryStage) {
            // 加载左侧文件列表
            initFileList();
            BorderPane root = new BorderPane();
            root.setLeft(fileList);
            root.setCenter(tabPane);
            Scene scene = new Scene(root,800,500);
            primaryStage.setTitle("文件浏览器");
            primaryStage.setScene(scene);
            primaryStage.show();
    }
    public void initFileList(){
        fileList.setPrefWidth(200);
        // 左侧加载小仙女目录下的文件
        File dir = new File("C:\\Users\\Administrator\\Desktop\\demo");
        File[] files = dir.listFiles();
        for(File f : files) {
            FileItem fitem = new FileItem(f);
            // 添加到左侧列表中
            fileList.data().add(fitem);
        }
        // 列表是鼠标事件响应
        fileList.setOnMouseClicked((MouseEvent event)->{
            // 如果左键单击的话
            if(event.getClickCount() == 1 && event.getButton() == MouseButton.PRIMARY) {
                oneClicked();
            }
        });
    }
    // 单击处理
    public void oneClicked() {
        // 获取列表选中模块,获取索引
        int index = fileList.getSelectionModel().getSelectedIndex();
        FileItem fitem = fileList.data().get(index);
        try {
            openFile(fitem);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    // 打开左侧文件
    public void openFile(FileItem fitem) throws Exception{
        // 查看选项卡是否打开
        Tab tab = findTab(fitem);
        if(tab != null) {
            // 设置为选中的选项卡
            // int pos = tabPane.getTabs().indexOf(tab);  // 获取id
            tabPane.getSelectionModel().select(tab);
            return;
        }
        // 打开一个新的选项卡并选中
        Node currentView = null;
        if(fitem.type == FileItem.TEXT) {
            // 文本文件处理
            String str = TextFileUtils.read(fitem.file, "UTF-8");
            TextArea t = new TextArea();
            t.setText(str);
            currentView = t;
        }else if(fitem.type == FileItem.IMAGE) {
            // 图片文件处理
            // 获取文件的本地路径
            Image image = new Image(fitem.file.toURI().toString());
            MyImagePane t = new MyImagePane();
            t.showImage(image);
            currentView = t;
        }else throw new Exception("不支持打开该格式");
        // 创建新的选项卡并选中
        tab = new Tab();
        tab.setText(fitem.firstName);
        tab.setContent(currentView);
        tabPane.getTabs().add(tab);
        tabPane.getSelectionModel().select(tab);
    }
    // 查看在右侧选项卡是否打开
    public Tab findTab(FileItem fitem) {
        ObservableList<Tab> tabs = tabPane.getTabs();
        for(Tab tab : tabs) {
            if(tab.getText().equals(fitem.firstName)) {
                return tab;
            }
        }
        return null;
    }
    public static void main(String[] args) {
        launch(args);
    }

案例 目录浏览器

打开一个目录 显示内文件名 大小 修改时间

package sample.file;
import java.io.File;
public class FileInfo {
    public File file;
    public String name; // 文件名
    public long lastModified; // 最后修改时间
    public long size;     // 文件大小
    public boolean isDir = false;   // 判断目录
    public FileInfo() {
    }
    public FileInfo(File f) {
        // 获取文件的一些属性
        this.file = f;
        name = f.getName();
        size = f.length();
        lastModified = f.lastModified();
        isDir = f.isDirectory();
    }
}
package sample.file;
import java.text.SimpleDateFormat;
import java.util.List;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.util.Callback;
public class FileBrowser extends TreeTableView<FileInfo> {
    // 根节点
    TreeItem rootItem = new TreeItem(new FileInfo());
    // 列
    TreeTableColumn<FileInfo, FileInfo> columns[] = new TreeTableColumn[3];
    public FileBrowser() {
        // 初始化列的设置
        initColumns();
        // 扁平化显示, 不显示根节点, 但必须要有根节点
        this.setRoot( rootItem );
        this.setShowRoot(false);
    }
    // 清空
    public void clear() {
        rootItem.getChildren().clear();
    }
    // 添加
    public void add(List<FileInfo> datalist) {
        for(FileInfo fi : datalist) {
            TreeItem item = new TreeItem(fi);
            rootItem.getChildren().add(item);
        }
    }
    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        // 动态设置列宽
        double w = this.getWidth();
        double w0 = w * 0.4;
        double w1 = w * 0.2;
        double w2 = w - w0 - w1- 20;
        columns[0].setPrefWidth(w0);
        columns[1].setPrefWidth(w1);
        columns[2].setPrefWidth(w2);
    }
    private void initColumns() {
        // 添加多个列
        columns[0] = new TreeTableColumn("文件名");
        columns[1] = new TreeTableColumn("大小");
        columns[2] = new TreeTableColumn("最后修改时间");
        this.getColumns().addAll(columns);
        // 重写layoutChildren() 动态调整个列的列宽
        // 设置 CellValueFactory (此段写法固定)
        Callback cellValueFactory = new Callback() {
            @Override
            public Object call(Object param) {
                CellDataFeatures p = (CellDataFeatures)param;
                return p.getValue().valueProperty();
            }
        };
        for(int i=0; i<columns.length; i++) {
            columns[i].setCellValueFactory(cellValueFactory);
        }
        // 设置CellFactory,定义每一列的单元格的显示
        // 这里使用了lambda表达式,否则写起来太长了!
        columns[0].setCellFactory((param)->{
            return new MyTreeTableCell("name");
        });
        columns[1].setCellFactory((param)->{
            return new MyTreeTableCell("size");
        });
        columns[2].setCellFactory((param)->{
            return new MyTreeTableCell("lastModified");
        });
    }
    // 单元格的显示
    static class MyTreeTableCell extends TreeTableCell<FileInfo,FileInfo> {
        static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String columnID;
        public MyTreeTableCell(String columnID) {
            this.columnID = columnID;
        }
        @Override
        protected void updateItem(FileInfo item, boolean empty) {
            super.updateItem(item, empty);
            if (empty || item == null) {
                setText(null);
                setGraphic(null);
            } else {
                setGraphic(null);
                if(columnID.equals("name")) {
                    this.setText(String.valueOf(item.name));
                }
                else if(columnID.equals("size")) {
                    if(item.isDir)  // 如果是目录则不显示大小
                        setText("目录");
                    else
                        setText(String.valueOf(item.size));
                }
                else if(columnID.equals("lastModified")) {
                    this.setText(sdf.format(item.lastModified)); // 格式化为年月日时分秒
                }
            }
        }
    }
}
package sample.file;
import java.io.File;
import java.util.ArrayList;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
    Stage stage; // 主窗口
    // 文件浏览功能
    FileBrowser fileBrowser = new FileBrowser();
    @Override
    public void start(Stage primaryStage) {
        this.stage = primaryStage;
            // 面板
            BorderPane root = new BorderPane();
            root.setCenter(fileBrowser);
            primaryStage.setTitle("目录练习");
            primaryStage.setScene( new Scene(root, 600, 400));
            primaryStage.show();
            // 按钮添加
            Button btnOpen = new Button("打开目录");
            root.setTop(btnOpen);
            btnOpen.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    // 当我点击按钮 打开窗口
                    openFolder();
                }
            });
    }
    // 打开目录
    private void openFolder() {
        // 选择一个目录
        DirectoryChooser chooser = new DirectoryChooser();
        chooser.setTitle("打开目录");
        // 展示目录
        File dir = chooser.showDialog(stage);
        // dir是用户选中的目录
        if (dir != null) {
            showFiles(dir);
        }
    }
    // 显示目录下的文件
    private void showFiles(File dir) {
        File[] files = dir.listFiles();
        if(files == null || files.length == 0){
            return;
        }
        ArrayList<FileInfo> datalist = new ArrayList<FileInfo>();
        for(File f : files) {
            datalist.add(new FileInfo( f));
        }
        // 显示这些文件/目录的列表
        fileBrowser.clear();
        fileBrowser.add( datalist );
    }
    public static void main(String[] args) {
        launch(args);
    }


相关文章
|
6月前
|
XML IDE Java
JavaFX 教程
JavaFX 教程
270 1
QGS
|
7月前
|
Android开发
JavaFX场景入门(上)
JavaFX场景入门
QGS
116 0
QGS
|
7月前
|
容器
JavaFX场景入门(下)
JavaFX场景入门
QGS
77 0
QGS
|
7月前
|
Java 容器
浅学JAVAFX布局
浅学JAVAFX布局
QGS
100 0
|
SQL 安全 小程序
javaFx 工具开发
javaFx 工具开发
|
IDE Java 开发工具
QGS
|
Java
JAVAFX+SceneBuilder基础入门
环境:JDK1.8+IDEA fxml载入项目 通过SceneBuilder编写好布局,生成fxml文件。
QGS
199 0
|
数据可视化 Java Android开发
JavaGUI:eclipse+e(fx)clipse+JavaFX Scene Builder搭建JavaFX可视化开发环境
JavaGUI:eclipse+e(fx)clipse+JavaFX Scene Builder搭建JavaFX可视化开发环境
163 0
JavaGUI:eclipse+e(fx)clipse+JavaFX Scene Builder搭建JavaFX可视化开发环境