На прикладі реалізації нестандартного для Swing поведінки меню., Статті, Java, статті

Автор:

При цьому наше меню повинне відображати піктограму та назву ітем –
піктограма зверху а текст знизу, при цьому коректно реагувати на ситуації
коли піктограма або текст відсутні, і мати можливість поміщати як
окремі елементи, так і елементи-контейнери.

    Таким чином завдання розбивається на наступні підзадачі:
  1. розширити функціональність класів які створюють меню щоб їх поведінка
    задовольняло поставленим вимогам
  2. коректно відмалювати наше меню.

Почнемо реалізацію з другої підзадачі.


Так як правильно і однотипно в панелі меню відображатися повинні і
елементи-контейнери (JMenu) І елементи-ітеми (JMenuItem)
напрошується рішення успадкувати наш клас від JMenu. Так ми і зробимо.
Все що потрібно буде змінити в нашому класі – це визначити розміри нашого
пункту меню і відмалювати його.

У класі оголошено кілька констант для відступів від кордонів пунктів меню і
між піктограмою і текстом, а також значення розміру для пункту меню по
замовчуванням, якщо ні текст ні піктограма не були задані. Також оголошені
екземплярність змінні icon і title для реалізації заявленої
функціональності – відображення піктограми і написи.

public class PictMenu extends JMenu {
static final long serialVersionUID = -1;

protected final int DEFAULT_WIDTH = 50;
protected final int DEFAULT_HEIGHT = 24;

protected final int TOP_DROP = 7;
protected final int PICT_DROP = 10;
protected final int BOTTOM_DROP = 7;
protected final int STR_DROP = 10;
protected final int BETWEEN_DROP = 0;

protected int px = 0;
protected int py = 0;
protected int sx = 0;
protected int sy = 0;

protected ImageIcon icon = null;
protected String title = "";


У класі імплементовані два методи і конструктор з параметрами. В
конструкторі инициализируем екземплярність змінні і визначаємо відповідні
розміри пункту меню.

    public PictMenu(String title, ImageIcon pict) {
super("");

this.title = title;
this.icon = pict;

Dimension dim = computeSize();
setPreferredSize(dim);
setMinimumSize(dim);
}

protected Dimension computeSize() {
int w = DEFAULT_WIDTH;
int h = DEFAULT_HEIGHT;

int separator = BETWEEN_DROP;

int strWidth = 0;
int strHeight = 0;

int pictWidth = 0;
int pictHeight = 0;

if ((title == null || title.length() == 0 ) && icon == null) {
return new Dimension(w, h);
}
else {
if (title == null || title.length() == 0) {
separator = 0;
}
else {
Font font = getFont();
FontMetrics fm = getFontMetrics(font);
strWidth = fm.stringWidth(title);
strHeight = fm.getHeight();
}

if (icon == null) {
separator = 0;
}
else {
pictWidth = icon.getIconWidth();
pictHeight = icon.getIconHeight();
py = TOP_DROP;
}

w = (pictWidth >= strWidth) ?
(pictWidth + PICT_DROP * 2) :
(strWidth + STR_DROP * 2);

px = (w – pictWidth) / 2;
sx = (w – strWidth) / 2;
}

h = TOP_DROP + pictHeight + separator + strHeight + BOTTOM_DROP;
sy = h – BOTTOM_DROP;

return new Dimension(w, h);
}


Другий метод відповідальний за відображення. Взагалі, щоб намалювати на
Swing-компоненті то що нам хочеться, досить перевизначити метод
paintComponent(Graphics). Метод оголошено в класі JСomponent і
служить для виклику делегованого paint() методу відповідного
ComponentUI обьекта. Метод isSelected() батьківського класу
JMenu повертає true якщо наш пункт меню був вибраний. У цьому
випадку ми додаємо промальовування рамки щоб візуально виділити обраний пункт і
зрушуємо картинку і напис на 1 піксель вниз-вправо.

    public void paintComponent(Graphics g) {
super.paintComponent(g);

if (isSelected()) {
g.setColor(new Color(244, 244, 244));
g.fillRect(0, 0, getWidth(), getHeight());

g.setColor(Color.WHITE);
g.drawLine(0, 0, getWidth() – 1, 0);
g.drawLine(0, 1, getWidth() – 2, 1);
g.drawLine(0, 2, getWidth() – 3, 2);

g.drawLine(0, 0, 0, getHeight() – 1);
g.drawLine(1, 0, 1, getHeight() – 2);
g.drawLine(2, 0, 2, getHeight() – 3);

g.setColor(Color.BLACK);
g.drawLine (1, getHeight () – 1, getWidth () – 1, getHeight () – 1);
g.drawLine (getWidth () – 1, 1, getWidth () – 1, getHeight () – 1);

g.setColor(new Color(204, 204, 204));
g.drawLine (2, getHeight () – 2, getWidth () – 2, getHeight () – 2);
g.drawLine (getWidth () – 2, 2, getWidth () – 2, getHeight () – 2);

g.setColor(new Color(224, 224, 224));
g.drawLine (3, getHeight () – 3, getWidth () – 3, getHeight () – 3);
g.drawLine (getWidth () – 3, 3, getWidth () – 3, getHeight () – 3);

g.setColor(Color.BLACK);
if (icon != null) g.drawImage(icon.getImage(), px + 1, py + 1, this);
if (title != null && title.length() > 0) g.drawString(title, sx + 1, sy + 1);
}
else {
if (icon != null) g.drawImage(icon.getImage(), px, py, this);
if (title != null && title.length() > 0) g.drawString(title, sx, sy);
}
}


Друга частина нашого завдання – зробити можливим додавання ітем в контейнер
JMenuBar. Здавалося б очевидне рішення – змінити поведінку контейнера
щоб він коректно обробляв додавання ітем. Однак насправді це не
буде найпростішим рішенням – доведеться переписати досить багато коду та
доведеться створити клас, який буде наслідувати JMenuItem і
промальовувати ітем аналогічно до того як ми зробили у першій частині завдання. Можна
піти іншим шляхом. У нас вже є клас який вміє себе правильно і
однотипно промальовувати в контейнері. Контейнер коректно розміщує інстанси
цього класу і передає повідомлення про дії користувача. Що нам потрібно – це
як то заховати випадаючий список включених елементів і дати можливість
програмісту реалізовувати реакцію на дії з цим пунктом меню.

Необхідність ховати випадаючий список з'являється через те, що навіть якщо
ми не помістимо жодного ітем в контейнер, то клас JMenu промальовує
рамку цього списку, такий квадратик 2х2 пікселя. Виглядає не дуже красиво. Цей
випадаючий список реалізований як popup-menu, координати для відображення якого
розраховуються контейнером. Доступ оголошено як private, Безпосередньо
дістатися до нього не можна. Але є public – Метод getPopupMenu(), І
ми встановимо для внутрішнього popup-menu розмір 0х0 пікселів. Все, більше ми його
не побачимо.

Отже, створюємо спадкоємця від раніше нами створеного класу PictMenu.

У конструкторі робимо виклик getPopupMenu().setPreferredSize(new
Dimension(0, 0));

Ще ми повинні діспетчерізовать події клавіатури, тому наш клас буде
реалізовувати інтерфейс KeyEventDispatcherі ми зареєструємо його
в DefaultKeyboardFocusManager.

public class SimplePictMenu extends PictMenu implements KeyEventDispatcher {
static final long serialVersionUID = -1;

public SimplePictMenu(String title, ImageIcon pict) {
super(title, pict);

getPopupMenu().setPreferredSize(new Dimension(0, 0));

DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager (). AddKeyEventDispatcher (this);

addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent me) {
sendCancelKeyEvent();
}

public void mouseClicked(MouseEvent me) {
sendCancelKeyEvent();
doAction();
}
});

}


Інтерфейс KeyEventDispatcherоголошує єдиний метод
dispatchKeyEvent в який передається подія клавіатури. Події
клавіатури, які необхідно обробити – натискання enter або пробіл – виклик
методу-обробника пункту меню, натискання курсорних клавіш і клавіші esc вже вміє
обробляти наш суперклас JMenu.

Тому ми оголосив метод з порожньою реалізацією doAction() який буде
перевизначатися і в якому буде код, що реалізовує дії при виборі пункту
меню.

    public void doAction() { 

}


Після виклику doAction() необхідно зняти виділення з пункту меню. Для
цього ми генеруємо подію, начебто користувач натиснув клавішу esc і передаємо
його для обробки викликом processKeyEvent().

    protected final void sendCancelKeyEvent() {
KeyEvent kee = new KeyEvent((Component)this, 401, 0l, 0, 27, (char)27);
dispatchKeyEvent(kee);
}

public boolean dispatchKeyEvent(KeyEvent e) {
KeyEvent kee = new KeyEvent((Component)this, e.getID(), e.getWhen(),
e.getModifiers (), e.getKeyCode (), e.getKeyChar ());

if (isSelected()) {
if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_SPACE) {
kee = new KeyEvent((Component)this, e.getID(), e.getWhen(),
e.getModifiers(), 27, (char)27);

doAction();
}
}
processKeyEvent(kee);

return true;
}


Щоб пункт меню так само реагував на дії миші реєструємо
MouseListener і імплементуємо методи mouseReleased() і
mouseClicked().

В результаті, створивши два зовсім простих класу ми отримали значне
зміна функціональності меню стандартного користувальницького інтерфейсу.

Повний код класів реалізують таке меню: SimplePictMenu.java.

Працюючий приклад можна побачити

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*