Я не очень знаком с Swing, но пытаюсь создать графический интерфейс Reddit. Наконец-то у меня все заработало, как и ожидалось, но я должен признать, что это довольно обыденно и не очень приятно смотреть. Я хочу добавить к нему несколько визуально привлекательных вещей, но, как я уже сказал, я новичок в Swing. Мне помогли сделать компоненты и тому подобное для моей идеи, так что я немного ошеломлен.
В настоящее время программа извлекает заголовки из верхней части субреддитов, вводимых пользователем. Как видно на скриншоте, смотреть на то, что я сказал, не очень приятно. Любые советы по дизайну будут оценены по достоинству!
PS Это определенно продвинуто для меня, поэтому просто хочу отметить, что я каким-то образом не создавал это с нуля без посторонней помощи. Также с тех пор исправили кривую иконку Reddit.
package com.Will.me;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Reddit extends JFrame {
static JPanel mainPanel = new JPanel();
static JTextField textField = new JTextField(20);
static JButton search = new JButton("Search");
static JButton[] numbers = new JButton[28];
static JButton[] headlines = new JButton[28];
static JLabel[] votes = new JLabel[28];
static JLabel title = new JLabel("tHeadlines from r/");
public Reddit(){
mainPanel.setLayout(null);
setUpComponents();
title.setVisible(false);
search.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
title.setVisible(true);
String str = textField.getText();
title.setText("");
title.setText("tHeadline from r/"+str);
getSubReddit(str);
}
});
add(mainPanel);
setTitle("Reddit");
setSize(900,600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void setUpComponents(){
JLabel redditIcon = new JLabel();
redditIcon.setBounds(10, 10, 100, 100);
resizeRedditLogo(redditIcon);
mainPanel.add(redditIcon);
textField.setBounds(160, 20, 250, 30);
search.setBounds(415, 23, 85, 25);
textField.setBackground(Color.decode("#8B0000"));
search.setOpaque(true);
search.setBorderPainted(false);
search.setBackground(Color.decode("#8B0000"));
search.setForeground(Color.WHITE);
search.setBorder(BorderFactory.createRaisedBevelBorder());
int y = 120;
for (int i = 0; i < numbers.length;i++){
numbers[i] = new JButton(String.valueOf(i+1));
numbers[i].setBounds(20, y, 40,30);
numbers[i].setBackground(Color.decode("#8B0000"));
numbers[i].setOpaque(true);
numbers[i].setForeground(Color.WHITE);
numbers[i].setBorder(BorderFactory.createLoweredSoftBevelBorder());
y+= 40;
numbers[i].setBorderPainted(false);
mainPanel.add(numbers[i]);
}
int y2 = 120;
for (int i = 0; i < headlines.length;i++){
headlines[i] = new JButton("");
headlines[i].setBounds(70, y2, 650,30);
headlines[i].setBackground(Color.decode("#8B0000"));
headlines[i].setOpaque(true);
headlines[i].setForeground(Color.WHITE);
headlines[i].setBorder(BorderFactory.createLoweredSoftBevelBorder());
y2+= 40;
headlines[i].setBorderPainted(false);
mainPanel.add(headlines[i]);
}
int y3 = 120;
JLabel voteTitle = new JLabel("# Votes");
voteTitle.setFont(new Font("Arial", Font.BOLD, 30));
voteTitle.setForeground(Color.WHITE);
voteTitle.setBounds(755, 70, 120, 30);
mainPanel.add(voteTitle);
for (int i = 0; i < votes.length;i++){
votes[i] = new JLabel("");
votes[i].setBounds(750, y3, 120,30);
votes[i].setForeground(Color.WHITE);
votes[i].setBorder(BorderFactory.createRaisedBevelBorder());
y3+= 40;
votes[i].setBorder(BorderFactory.createRaisedBevelBorder());
mainPanel.add(votes[i]);
}
textField.setForeground(Color.WHITE);
title.setBorder(BorderFactory.createRaisedBevelBorder());
title.setBounds(105, 70, 605, 30);
title.setFont(new Font("Arial", Font.BOLD, 18));
title.setForeground(Color.WHITE);
mainPanel.add(title);
mainPanel.setBackground(Color.RED);
mainPanel.add(textField);
mainPanel.add(search);
}
public static void getSubReddit(String sub){
try
{
Document doc = Jsoup.connect("https://old.reddit.com/r/"+sub).get();
Elements el = doc.select("p.title");
Elements score = doc.select("div.score.unvoted");
int i,j;
for (i=0,j=0;i<el.size() && j<score.size();i++,j++){
headlines[i].setText(el.get(i).text());
votes[j].setText(score.get(j).text());
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
public static void resizeRedditLogo(JLabel label){
try {
ImageIcon imageIcon = new ImageIcon(new ImageIcon("../RedditGUI/src/reddit1.png").getImage().getScaledInstance(120, 120, Image.SCALE_DEFAULT));
label.setIcon(imageIcon);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Reddit();
}
2 ответа
Что касается самого кода, вы должны попытаться отделить представление от «логики», применив один из различных шаблонов (наиболее часто используемые MVC и MVP), как сказал Гилбер Ла Блан в своем комментарии:
Что касается визуальной части, то проще создавать компоненты, которые обеспечивают функциональность (-ы) высокого уровня и которые вы используете для составления основного представления.
Для «разметки» почему бы не использовать JList с настраиваемым средством визуализации для отображения каждой строки. Если вам нужны интерактивные кнопки (которые, насколько я вижу в вашем коде, не реализованы), вы можете использовать Коробка или BoxLayout.
Вступление. Извините, я перескочил на этот вопрос, не удосужившись прочитать его в деталях. Поскольку вы новичок, вам нужно больше сказать, чтобы дать полезный ответ. И я предпочитаю создать новый, а не редактировать первый.
Легкий путь,
Если вы не хотите так сильно меняться:
Вы можете попробовать удалить статические поля. В идеале ничего не должно быть статичным вместо
main
точка входа.Вы также должны попытаться извлечь синтаксический анализ в специальный класс. Это приведет к лучшему разделению задач и инкапсуляции и позволит вам протестировать или поменять местами позже.
Создайте компоненты. Отдельные компоненты приведут в порядок и прояснят конструкцию вашего пользовательского интерфейса.
Используйте менеджеры компоновки для размещения компонентов.
Трудный путь,
Так да. Как сказано в моем более простом ответе и Жильбер Ле Блан вам следует попытаться реализовать шаблон, чтобы отделить пользовательский интерфейс от «логики». В MVC шаблон является распространенным в приложениях с графическим интерфейсом пользователя, он также используется расширенными компонентами Swing.
Тогда почему бы не попробовать создать три класса:
- А модель которые представляют состояние вашего приложения; список результатов.
- А Посмотреть который содержит графические компоненты, используемые для отображения результатов модели.
- А контролер чтобы получать действия из представления и соответствующим образом обновлять модель.
Модель
Ваша модель едва ли List<Subreddit>
где Subreddit
это DTO с title
и score
. Но ваше представление должно быть уведомлено при изменении этого списка, поэтому вам нужно создать для него специальный класс и добавить некоторый шаблон для регистрации и уведомления одного (или нескольких) слушателей.
Вид
Ваше мнение — это ваше JFrame
(или любой Container
отображается внутри JFrame
). Это представление должно прослушивать регистрацию слушателя в модели, чтобы он мог обновлять его при изменении результатов.
class RedditAppView extends JFrame {
public RedditAppView(RedditAppModel model) {
model.addModelListener(new RedditAppModel.ModelAdapter(){
public void onResultsChanged(List<Subbreddit> results) {
refresh();
}
});
}
}
Проще использовать LayoutManager
чем использование абсолютной компоновки в ваших представлениях, потому что менеджер компоновки позаботится о многих частях при изменении размера вашего приложения и при изменении компонента. я люблю GridBagLayout
, это подробный макет (но Java и Swing тоже подробны), но с его помощью можно добиться почти всего, и об этом довольно легко рассуждать. Однако создание и поддержание большого вида может быть очень сложным. Вот почему мы обычно создаем компоненты для инкапсуляции функциональности. В вашем случае вы можете создать:
- А
Logo
который сам позаботится об изменении размера. - А
SearchForm
который содержитJTextField
а такжеJButton
добавить приемник одного события, чтобы получать уведомления, когда пользователь нажимает кнопку, или нажимает клавишу ввода, или подождите 3 секунды, .. это одно из преимуществ компонентности; вы можете развивать одну часть своего приложения, не затрагивая остальной код. - А список результатов. Это может быть
JList
внутриJScrollPane
. Вы можете настроить пользовательский рендерер в этом списке для рендерингаSubreddit
более привлекательным способом, чем по умолчаниюtoString
.
Ваша иерархия представлений может выглядеть так:
RedditAppView
|- Container
| |- Logo
| |- SearchForm
| |- ResultList
| | |- JLabel ("# Votes")
| | |- JScrollPane
| | | |- JList with Renderer
Если вы используете предложенную модель, вы можете создать класс, который адаптирует ваш RedditAppModel
к JListModel
. Делая это, вам не нужно refresh
самостоятельно список результатов будет обновлен автоматически:
class ResultListModelAdapter extends DefaultListModel<Subreddit> {
private final RedditAppModel model;
private ResultListModelAdapter(RedditAppModel model) {
this.model = model;
this.model.addModelListener(new RedditAppModel.ModelAdapter() {
@Override
public void onResultsChanged(List<Subreddit> results) {
int end = Math.max(getSize(), results.size()); // Not sure it is needed
fireContentsChanged(this, 0, end);
}
});
}
@Override
public int getSize() {
return model.getResultsCount()
}
@Override
public Subreddit getElementAt(int index) {
return model.getResultAt(index);
}
}
Контроллер
Класс, который будет обрабатывать действия пользователя и отражать их в модели. У вашего контроллера будет один search
действие вызывается при получении представления.
class RedditAppController {
private final RedditAppModel model;
public RedditAppController(RedditAppModel model, RedditApi api) {
this.model = model;
this.api = api;
}
void onSearch(String term) {
List<Subreddit> results = // Jsoup things
model.setResults(results);
}
}