Форматирование Java JList и функциональность JTable

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

  1. Невозможность перенести мой список на основе буквенно-цифровых клавиш. Это основной способ конечного пользователя быстро выполнять поиск в списке, так как при заполнении он может иметь длину более 800 строк. (пример: ввод "s" для перехода к той части списка, которая начинается с "s")

  2. Визуально я не фанат выбора строк с помощью JTables; при наличии структуры из двух столбцов очевидно, что между двумя столбцами есть разрыв, в отличие от JList - приятного плавного выделенного выделенного фрагмента. Все линии сетки были отключены, а также использование редактирования ячеек в качестве FYI.

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

  package mosaiqToCTWorklist;

import java.awt.Font;
import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;

public class PatientSelectionWindow extends JFrame implements ActionListener {

    private static final long serialVersionUID = 1L;
    private JPanel contentPane;
    private JRadioButton rdbtnActivePatients;
    private JRadioButton rdbtnInactivePatients;
    private JRadioButton rdbtnAllPatients;
    private JButton btnSendSelection;
    private JList<String> patients;
    private JScrollPane scrollBar;
    private ButtonGroup btngroup;
    private DefaultListModel<String> model = new DefaultListModel<String>();
    private List<PatientData> ptList;
    //  private HashMap<String,PatientData> ptRecords;
    private Label lblPatientTotal;
    private Label lblDOB;
    private Label lblAge;
    private Label lblGender;
    private Label label_4;


    public PatientSelectionWindow(List<PatientData> ptList) {
        this.ptList = ptList;
        activePatients();
        createWindow();
    }

    public void createWindow() {
        setResizable(false);
        setTitle("Mosaiq to CT Worklist");
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setBounds(100, 100, 534, 378);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);

        patients = new JList<String>(model);
        scrollBar = new JScrollPane(patients);
        scrollBar.setBounds(10, 11, 376, 328);
        scrollBar.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        contentPane.add(scrollBar);

        rdbtnActivePatients = new JRadioButton("Active Patients");
        rdbtnActivePatients.addActionListener(this);
        rdbtnActivePatients.setSelected(true);
        rdbtnActivePatients.setFont(new Font("Tahoma", Font.PLAIN, 13));
        rdbtnActivePatients.setBounds(392, 35, 130, 23);
        contentPane.add(rdbtnActivePatients);

        rdbtnInactivePatients = new JRadioButton("Inactive Patients");
        rdbtnInactivePatients.addActionListener(this);
        rdbtnInactivePatients.setFont(new Font("Tahoma", Font.PLAIN, 13));
        rdbtnInactivePatients.setBounds(392, 61, 130, 23);
        contentPane.add(rdbtnInactivePatients);

        rdbtnAllPatients = new JRadioButton("All Patients");
        rdbtnAllPatients.addActionListener(this);
        rdbtnAllPatients.setFont(new Font("Tahoma", Font.PLAIN, 13));
        rdbtnAllPatients.setBounds(392, 86, 130, 23);
        contentPane.add(rdbtnAllPatients);

        btngroup = new ButtonGroup();
        btngroup.add(rdbtnActivePatients);
        btngroup.add(rdbtnInactivePatients);
        btngroup.add(rdbtnAllPatients);

        btnSendSelection = new JButton("Send Selected");
        btnSendSelection.addActionListener(new DicomTransfer());
        btnSendSelection.setFont(new Font("Tahoma", Font.PLAIN, 13));
        btnSendSelection.setBounds(396, 154, 126, 38);
        contentPane.add(btnSendSelection);

        Label label = new Label("Show");
        label.setFont(new Font("Dialog", Font.BOLD, 11));
        label.setBounds(392, 10, 62, 22);
        contentPane.add(label);

        lblPatientTotal = new Label("");
        setPatientCount();
        lblPatientTotal.setBounds(392, 115, 130, 29);
        contentPane.add(lblPatientTotal);

        Label label_1 = new Label("D.O.B:");
        label_1.setBounds(396, 252, 38, 22);
        contentPane.add(label_1);

        Label label_2 = new Label("Age:");
        label_2.setBounds(396, 226, 30, 22);
        contentPane.add(label_2);

        Label label_3 = new Label("Gender:");
        label_3.setBounds(396, 280, 46, 22);
        contentPane.add(label_3);

        lblDOB = new Label("1984-10-10");
        lblDOB.setBounds(434, 252, 88, 22);
        contentPane.add(lblDOB);

        lblAge = new Label("64");
        lblAge.setBounds(426, 226, 38, 22);
        contentPane.add(lblAge);

        lblGender = new Label("male");
        lblGender.setBounds(444, 280, 62, 22);
        contentPane.add(lblGender);

        label_4 = new Label("Currently Selected Pt.");
        label_4.setFont(new Font("Dialog", Font.BOLD, 11));
        label_4.setBounds(392, 198, 130, 22);
        contentPane.add(label_4);
    }

    private void setPatientCount() {
        Integer patientTotal = model.size();
        lblPatientTotal.setText("Total Patients: " + patientTotal.toString());
    }

    private void activePatients() {
        model.clear();
        for(PatientData pt : ptList){
            if (pt.getActiveStatus() == 1) {
                model.addElement(pt.getLastName() + ", " + pt.getFirstName() + " "
                        + pt.getMiddleName() + " " + pt.getMHN() );
            }
        }
    }

    private void inActivePatients() {
        model.clear();
        for(PatientData pt : ptList){
            if (pt.getActiveStatus() == 0) {
                model.addElement(pt.getLastName() + ", " + pt.getFirstName() + " "
                        + pt.getMiddleName() + " " + pt.getMHN() );
            }
        }
    }

    public void allPatients() {
        model.clear();
        for(PatientData pt : ptList){
            model.addElement(pt.getLastName() + ", " + pt.getFirstName() + " "
                    + pt.getMiddleName() + " " + pt.getMHN() );
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == rdbtnActivePatients) {
            activePatients();
        }
        if (e.getSource() == rdbtnAllPatients) {
            allPatients();
        }
        if (e.getSource() == rdbtnInactivePatients) {
            inActivePatients();
        }

        patients.setModel(model);
        setPatientCount();
    }
    }

(да, я знаю, что я использую фрукты …… Я голоден) Примеры того, что я получаю, и того, что больше того, на что я надеялся. Я просто чувствую, что конечному пользователю легче читать.

1 ответ

Решение

Давайте начнем с разницы между моделями и представлениями и их обязанностями.

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

Ответственность за решение о том, как лучше показать эти данные, лежит на представлении.

Какое все это имеет отношение к вашему вопросу? На самом деле, каждая вещь. Модель не должна пытаться предварительно форматировать данные, а должна содержать необработанные данные. Ответственность за представление этих данных лежит на представлении.

Например, у вас может быть один JList который показывает данные пациента в виде {mhn} {first name} {last name}, но другой JList что хочет {lastname}, {first name} {givenname}...

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

Итак, вместо...

private DefaultListModel<String> model = new DefaultListModel<String>();
private List<String> ptList;

Вы хотите использовать что-то более похожее на...

private DefaultListModel<PatientData> model = new DefaultListModel<PatientData>();
private List<PatientData> ptList;

Теперь это поднимет следующий вопрос "как вы форматируете необработанные данные?". Swing предоставляет концепцию средств визуализации (и в некоторых случаях редакторов), которые предоставляют расширяемый и подключаемый API, который предоставляет вам средства, с помощью которых вы можете предоставить "форматтер" для таких классов, как JList, JTable а также JTree, Это важная концепция для понимания, так как она широко используется в Swing.

Начните с изучения использования списков и написания собственного средства визуализации ячеек.

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

Одним из подходов было бы создать наш собственный ListCellRenderer с нуля. Это проблематично, так как большая часть форматирования ячейки происходит от DefaultListCellRendererпоэтому мы должны обеспечить все форматирование самостоятельно, и поскольку мы используем несколько компонентов, это усугубляет проблему.

Следующий рендер будет производить следующий вывод...

Рендеринг с помощью панели

public class NameFirstWithMNHListCellRenderer extends JPanel implements ListCellRenderer<PatientData> {

    private static final Border SAFE_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);
protected static Border noFocusBorder = DEFAULT_NO_FOCUS_BORDER;

    private JLabel name;
    private JLabel mhn;

    public NameFirstWithMNHListCellRenderer() {
        name = new JLabel();
        mhn = new JLabel();
        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.weightx = 1;
        gbc.anchor = GridBagConstraints.WEST;
        add(name, gbc);
        gbc.gridx++;
        gbc.weightx = 0;
        gbc.anchor = GridBagConstraints.EAST;
        add(mhn, gbc);
    }

    @Override
    public Component getListCellRendererComponent(JList<? extends PatientData> list, PatientData value, int index, boolean isSelected, boolean cellHasFocus) {
        name.setText(value.getFirstName() + " " + value.getLastName());
        mhn.setText(value.getMHN());
        formatBackground(name, list, index, isSelected, cellHasFocus);
        formatBackground(mhn, list, index, isSelected, cellHasFocus);
        formatBackground(this, list, index, isSelected, cellHasFocus);
        formatForeground(name, list, index, isSelected, cellHasFocus);
        formatForeground(mhn, list, index, isSelected, cellHasFocus);
        formatForeground(this, list, index, isSelected, cellHasFocus);

        formatBorder(this, list, index, isSelected, cellHasFocus);

        return this;
    }

    private Border getNoFocusBorder() {
    Border border = UIManager.getBorder("List.cellNoFocusBorder");
    if (System.getSecurityManager() != null) {
        if (border != null) return border;
        return SAFE_NO_FOCUS_BORDER;
    } else {
        if (border != null &&
                (noFocusBorder == null ||
                noFocusBorder == DEFAULT_NO_FOCUS_BORDER)) {
            return border;
        }
        return noFocusBorder;
    }
}

    protected void formatBorder(JComponent comp, JList<? extends PatientData> list, int index, boolean isSelected, boolean cellHasFocus) {
        Border border = null;
        if (cellHasFocus) {
            if (isSelected) {
                border = UIManager.getBorder("List.focusSelectedCellHighlightBorder");
            }
            if (border == null) {
                border = UIManager.getBorder("List.focusCellHighlightBorder");
            }
        } else {
            border = getNoFocusBorder();
        }
        comp.setBorder(border);
    }

    protected void formatBackground(JComponent comp, JList<? extends PatientData> list, int index, boolean isSelected, boolean cellHasFocus) {
        Color bg = null;

        JList.DropLocation dropLocation = list.getDropLocation();
        if (dropLocation != null
                && !dropLocation.isInsert()
                && dropLocation.getIndex() == index) {

            bg = UIManager.getColor("List.dropCellBackground");

            isSelected = true;
        }

        if (isSelected) {
            comp.setBackground(bg == null ? list.getSelectionBackground() : bg);
        } else {
            comp.setBackground(list.getBackground());
        }

        comp.setOpaque(isSelected);

        setEnabled(list.isEnabled());
    }

    protected void formatForeground(Component comp, JList<? extends PatientData> list, int index, boolean isSelected, boolean cellHasFocus) {
        Color fg = null;

        JList.DropLocation dropLocation = list.getDropLocation();
        if (dropLocation != null
                && !dropLocation.isInsert()
                && dropLocation.getIndex() == index) {

            fg = UIManager.getColor("List.dropCellForeground");

            isSelected = true;
        }

        if (isSelected) {
            comp.setForeground(fg == null ? list.getSelectionForeground() : fg);
        } else {
            comp.setForeground(list.getForeground());
        }

        comp.setEnabled(list.isEnabled());
        comp.setFont(list.getFont());

    }
}

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

Короче говоря, JList просто не предназначен для поддержки структурированных данных таким образом, поэтому мы имеем JTable...

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

Давайте начнем с TableModel...

public static class PatientTableModel extends AbstractTableModel {

    protected static final String[] COLUMN_NAMES = {
        "First name",
        "Last name",
        "MHN"
    };

    private List<PatientData> rowData;

    public PatientTableModel() {
        rowData = new ArrayList<>(25);
    }

    public void add(PatientData... pd) {
        add(Arrays.asList(pd));
    }

    public void add(List<PatientData> pd) {
        rowData.addAll(pd);
        fireTableDataChanged();
    }

    @Override
    public int getRowCount() {
        return rowData.size();
    }

    @Override
    public int getColumnCount() {
        return COLUMN_NAMES.length;
    }

    @Override
    public String getColumnName(int column) {
        return COLUMN_NAMES[column];
    }

    public PatientData getPatientDataAt(int row) {
        return rowData.get(row);
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        PatientData pd = getPatientDataAt(rowIndex);
        Object value = null;
        switch (columnIndex) {
            case 0:
                value = pd.getFirstName();
                break;
            case 1:
                value = pd.getLastName();
                break;
            case 2:
                value = pd.getMHN();
                break;
        }
        return value;
    }

}

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

Далее нам нужно подготовить JTable...

patients = new JTable(model);
patients.setShowGrid(false);
patients.setShowHorizontalLines(false);
patients.setShowVerticalLines(false);
patients.setRowMargin(0);
patients.setIntercellSpacing(new Dimension(0, 0));
patients.setFillsViewportHeight(true);
TableRowSorter<PatientTableModel> sorter = new TableRowSorter<>(model);
patients.setRowSorter(sorter);

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

Далее нам нужно подготовить модель...

public PatientSelectionWindow(List<PatientData> ptList) {
    this.ptList = ptList;
    model.add(ptList);
    createWindow();
    activePatients();
}

Вы увидите через минуту, вам больше не нужно ptList...

Далее нам нужно изменить фильтрацию...

private void activePatients() {
    RowFilter<PatientTableModel, Integer> rf = new RowFilter<PatientTableModel, Integer>() {

        @Override
        public boolean include(RowFilter.Entry<? extends PatientTableModel, ? extends Integer> entry) {
            int row = entry.getIdentifier();
            PatientData pd = entry.getModel().getPatientDataAt(row);
            return pd.getActiveStatus() == 1;
        }

    };
    ((TableRowSorter) patients.getRowSorter()).setRowFilter(rf);
    setPatientCount();
}

private void inActivePatients() {
    RowFilter<PatientTableModel, Integer> rf = new RowFilter<PatientTableModel, Integer>() {

        @Override
        public boolean include(RowFilter.Entry<? extends PatientTableModel, ? extends Integer> entry) {
            int row = entry.getIdentifier();
            PatientData pd = entry.getModel().getPatientDataAt(row);
            return pd.getActiveStatus() != 1;
        }

    };
    ((TableRowSorter) patients.getRowSorter()).setRowFilter(rf);
    setPatientCount();
}

public void allPatients() {
    ((TableRowSorter) patients.getRowSorter()).setRowFilter(null);
    setPatientCount();
}

Три метода в основном используют преимущества RowFilter поддержка доступна в рамках JTable API, это означает, что вам не нужно физически касаться модели или менять ее или управлять данными по отдельности, это все позаботится о вас...

И тогда мы получим следующий результат...

Взгляни на:

Больше подробностей

Другие вопросы по тегам