Создаём свой Look and Feel — Часть I

  • Tutorial
Добрый день, Хабражитель!

Уже достаточно много было рассказано о Swing и графике, также немного было упомянуто о различных незаурядных возможностях, предоставляемых сторонними библиотеками.

Сегодня же я представлю на Ваш суд «последний рубеж» погружения во все те возможности, которые предлагает нам Swing – создание своего Look and Feel класса, а также всего того, что потребуется по ходу дела.

Сразу скажу — если Вы хотите взяться за написание своего примера, а не просто «подсмотреть» пару интересных моментов — будьте готовы угробить потратить на это дело много времени, если Вы конечно собираетесь выпустить что-то более-менее живое и юзабельное в любых приложениях в качестве «обёртки», а также собираетесь в дальнейшем поддерживать созданный вами LaF.

Но даже если Вы не собираетесь делать свой LaF – советую всё же ознакомиться с содержанием. Уверен Вы найдёте много нового и интересного. И даже, возможно, поймёте в чём был корень той или иной интерфейсной проблемы, которая, вероятно, мучала Вас долгие месяцы и годы.

Tip: Если Вы не ещё не знакомы с графикой и Swing в Java более-менее тесно — советую сперва перечитать несколько статей на тему (к примеру оффициальный туториал, статью по Swing от Skipy или же мои предыдущие вводные статьи).

MyLookAndFeel.java


Итак, не теряя времени, начнём с основы — сам LookAndFeel класс. Пронаследуем наш MyLookAndFeel класс от BasicLookAndFeel, а не «пустого» LookAndFeel, так как в нём уже реализованы многие вещи, как то: константы для различных UI-классов, стандартные горячие клавиши компонентов, цветовые схемы и многое другое, без чего большинство UI-классов даже не сможет корректно инициализироваться (тем более что большую часть этих переменных нет необходимости переопределять).

Итак, используем готовый BasicLookAndFeel и переопределим несколько обязательных методов, описывающих наш будущий LaF:
public class MyLookAndFeel extends BasicLookAndFeel
{
    public String getDescription ()
    {
        // Описание LaF
        return "Cross-platform Java Look and Feel";
    }

    public String getName ()
    {       
        // Имя LaF
        return "MyLookAndFeel";
    }

    public String getID ()
    {           
        // Уникальный идентификатор LaF
        return getName ();
    }

    public boolean isNativeLookAndFeel ()
    {               
        // Привязан ли данный LaF к текущей платформе (является ли его нативной имплементацией)
        return false;
    }

    public boolean isSupportedLookAndFeel ()
    {
        // Поддерживается ли данный LaF на текущей платформе
        return true;
    }
}

Теперь остаётся применить одну «волшебную» строку перед созданием любого приложения и оно будет использовать наш LaF:
UIManager.setLookAndFeel MyLookAndFeel.class.getCanonicalName () );

Вот что мы получим на простом примере:


У данного LaF'а уже есть один заметный плюс — он полностью кросс-платформенный, т. е. в текущей реализации он будет выглядеть ужастно одинаково что на Windows, что на Mac OS, что на Linux системах.

Остаётся поработать над косметикой — именно этим мы и займёмся!..

Стилизация компонентов


Итак, нам потребуется несколько вещей для видоизменения каждого компонента, начнём с самой очевидной — создание UI класса для выбранного компонента.

Для простоты возьмём кнопку — наиболее знакомый кому бы то ни было компонент.
По аналогии с BasicLookAndFeel для каждого J-компонента существует BasicUI-реализация, которая сильно упрощает и сводит к минимуму необходимые для стилизации компонентов операции. Для кнопки, не сложно догадаться, это BasicButtonUI – именно его мы и будем наследовать и переопределять.

Во всех UI-классах помимо специализированных методов (для каждого класса они свои, в зависимости от компонента, для которого данный UI) есть несколько стандартных методов, унаследованных от ComponentUI — для начала рассмотрим их:

  1. public void installUI ( JComponent c )
    Данный метод предназначен для «инсталляции» UI-класса на определённый J-компонент. Именно в этом методе стоит определять какие-либо слушатели для J-компонента или какие-либо другие данные, необходимые для корректной отрисовки и работы Вашего UI.
    Вызывается этот метод из updateUI () при создании любого J-компонента.

  2. public void uninstallUI ( JComponent c )
    В данном методе весьма желательно удалять все добавленные Вами на компонент слушатели, а также очищать память от ненужных более данных.
    Вызывается этот метод из setUI ( ComponentUI newUI ) при смене у компонента его текущего UI, или же при полном удалении компонента и «очищении» его зависимостей.

  3. public void paint ( Graphics g, JComponent c )
    Данный метод занимается непосредственно отрисовкой компонента. Именно этот метод является ключевым в UI-классах, так как он определяет визуальное оформление компонента при любом его состоянии.
    Данный метод вызывается косвенно при любой отрисовке/перерисовке компонента из paintComponent ( Graphics g ), который присутствует у любого наследника JСomponent класса, т. е. данный метод отрабатывает только при реальной потребности перерисовки, а также с учётом необходимого clip'а (всё это реальзовано в paintComponent ( Graphics g )).

  4. public void update ( Graphics g, JComponent c )
    Данный метод является лишь небольшим дополнением к paint ( Graphics g, JComponent c ), но именно он вызывается из paintComponent ( Graphics g ) у JСomponent'ов и далее вызывает описанный ранее paint ( Graphics g, JComponent c ) из UI-класса. По факту данный метод реализует затирание «устаревшего» фона объекта и далее отрисовку по «чистому листу».

  5. public Dimension getPreferredSize ( JComponent c )
    Данный метод определяет желаемый размер компонента, на который установлен данный UI-класс.
    Вызывается он из одноимённого метода getPreferredSize () у JСomponent'а или любого его наследника при необходимости пересчёта желаемого размера компонента.

  6. public Dimension getMinimumSize ( JComponent c )
    public Dimension getMaximumSize(JComponent c)

    Два данных метода определяют минимальный и максимальный размеры компонентов и могут быть учтены различными Layout-менеджерами при ресайзинге. По умолчанию они возвращают то же значение, что и getPreferredSize ( JComponent c ), реализованный в UI-классе и описанный ранее.

  7. public boolean contains ( JComponent c, int x, int y )
    Данный метод определяет принадлежит ли точка данному компоненту, или же нет. Необходим он, по большей части, для корректной обработки событий мыши. При любом событии мыши, оно передаётся самому верхнему компоненту, «замеченному» в данной точке. И именно для того, чтобы узнать какой из компонентов находится на самом верху в заданной точке последовательно опрашиваются компоненты, располагающиеся там. Т. е. Засчёт данного метода можно, например, заставить события проходить «мимо» Вашего компонента в тех местах, где он фактически не отрисован и необходимости в получении события нет (например пустая область справа от табов или же угловые области у круглой кнопки).
    Вызывается данный метод из contains ( int x, int y ), присутствующего у всех наследников класса JComponent.
Ещё об одном важном методе я расскажу чуть позже…

Tip: В ComponentUI есть ещё несколько методов, но они нам сегодня не понадобятся и весьма вряд ли будут использованы Вами когда-либо, поэтому я сэкономлю время и позволю себе пропустить их описание.

Дабы не запутаться, ещё раз приведу список упомянутых классов/методов:

JComponent
  • updateUI ()
  • setUI ( ComponentUI newUI )
  • paintComponent ( Graphics g )
  • getPreferredSize ()
  • contains ( int x, int y )
ComponentUI
  • public void installUI ( JComponent c )
  • public void uninstallUI ( JComponent c )
  • public void paint ( Graphics g, JComponent c )
  • public void update ( Graphics g, JComponent c )
  • public Dimension getPreferredSize ( JComponent c )
  • public Dimension getMinimumSize ( JComponent c )
  • public Dimension getMaximumSize ( JComponent c )
  • public boolean contains ( JComponent c, int x, int y )
Собственно, на основе этих базовых методов из ComponentUI и строятся все UI-классы любых компонентов. Для упрощения Вашей жизни в BasicUI-классах также представлено множество других методов, конкретизирующих и упрощающих отрисовку того или иного компонента.

Ещё немного о LookAndFeel


Добавлю ещё немного описания построения LookAndFeel класса, чтобы в дальнейшем больше к нему не возвращаться.

Помимо описанной выше возможности указания UI классов для стандартных компонентов в UIDefaults при нициализации LookAndFeel'а есть также и множество других вещей, которыми заправляет LookAndFeel класс. Большинство из них Вам вряд ли потребуется реализовывать/переопределять, так как они уже есть в BasicLookAndFeel классе, но для порядка стоит знать, что же там творится.

Итак, рассмотрим методы представленные в классе LookAndFeel:
  1. installColors ( JComponent c, String defaultBgName, String defaultFgName )
    installColorsAndFont ( JComponent c, String defaultBgName, String defaultFgName, String defaultFontName )
    installBorder ( JComponent c, String defaultBorderName )
    uninstallBorder ( JComponent c )
    installProperty ( JComponent c, String propertyName, Object propertyValue )
    makeKeyBindings ( Object[] keyBindingList )
    makeInputMap ( Object[] keys )
    makeComponentInputMap ( JComponent c, Object[] keys )
    loadKeyBindings ( InputMap retMap, Object[] keys )
    makeIcon ( final Class<?> baseClass, final String gifFile )
    getDesktopPropertyValue ( String systemPropertyName, Object fallbackValue )

    Весь этот огромный набор статичных методов имеется в LookAndFeel классе фактически для одной цели — упрощённого использования ключей и установки значений, получаемых по этим ключам, различным компонентам. Будь то цвет фона или шрифта, горячие клавиши для текстовых полей или бордер компонента. Используются они по большей части в отдельных UI-классах и в UIDefaults. Особого смысла описывать назначение каждого отдельного метода нет, так как оно более чем подробно описано прямо в комментариях перед каждым методом.

  2. public LayoutStyle getLayoutStyle ()
    Данный метод возвращает инстанс класса LayoutStyle, который определяет поведение компонентов в различных лэйаутах. К примеру он может определять какой отступ предпочтителен между лэйблом и текстовым полем, расположенными друг за другом на одном контейнере (да, именно вплоть до таких конкретных случаев). Впрочем это достаточно абстрактная вещь и в реальности мало какие лэйауты используют определённые в этом классе параметры и этот момент даже отметили разработчики в комментариях к классу. К примеру в Swing единственным использующим LayoutStyle лэйаутом является GroupLayout, что и не странно, учитывая его специфику.

  3. public void provideErrorFeedback ( Component component )
    Данный метод вызывается каждый раз, когда пользователь производить какое-либо недопустимое действие (к примеру нажимает Backspace в пустом текстовом поле или пытается удалить текст нередактируемого поля и т. п.). В стандартной реализации подобные случае просто вызывают метод Toolkit.getDefaultToolkit ().beep (), который воспроизводит короткий звуковой сигнал некорректности операции, стандартный для текущей ОС. Впрочем, Вы всегда можете изменить данное поведение на любое другое (к примеру вовсе убрать этот раздражающий *beep*).

  4. public Icon getDisabledIcon ( JComponent component, Icon icon )
    Данный метод возвращает соответствующее текущему LookAndFeel'у отображение заблокированной иконки. Этот метод используется для отображения иконок заблокированной кнопки, лэйбла и заблокированных элементов других стандартных компонентов. Если у Вас есть более удачный/быстрый способ создания «заблокированного» вида иконки — дерзайте переопределить данный метод!

  5. public Icon getDisabledSelectedIcon ( JComponent component, Icon icon )
    Этот метод идентичен предыдщему, только работает для «выбранных» иконок. В стандартной реализации банально вызывает предыдущий метод.

  6. public String getName ()
    public String getID ()
    public String getDescription ()
    public boolean isNativeLookAndFeel ()
    public boolean isSupportedLookAndFeel ()

    Эти методы уже были описаны ранее при создании LookAndFeel класса. Они просто предоставляют базовую информацию о данном LookAndFeel'е.

  7. public boolean getSupportsWindowDecorations ()
    Данный метод более интересен — он сообщает UIManager'у, способен ли RootPaneUI, возвращаемый данным LookAndFeel'ом декорировать окно. Т. е. создавать оформление окна заместо стандартного системного оформления. В случае если данный метод возвращает true, Вы реализовали специфичное оформление окна в Вашем RootPaneUI – достаточно выполнить два следующих метода:
    JFrame.setDefaultLookAndFeelDecorated ( true );
    JDialog.setDefaultLookAndFeelDecorated ( true );
    для обёртки любых инстансов JDialog/JFrame в специальное оформление. О том же, как создать подобное оформление, дабы не распыляться, я напишу отдельным топиком в своё время.

  8. public void initialize ()
    public void uninitialize ()

    Данные методы вызываются непосредственно при установке и, соответственно, смене LookAndFeel'а через UIManager. В них можно завернуть создание каких-либо глобальных слушателей и создание каких-либо переменных, необходимых для корректной работы Вашего LookAndFeel'а.

  9. public UIDefaults getDefaults()
    Данный метод является, по большому счёту, основой базового оформления всех компонентов. Он предоставляет всё то огромное множество различных переменных (шрифты, цвета, хоткеи и пр.) необходимое для работы LookAndFeel'а и отдельных его UI классов. В BasicLookAndFeel данный метод разбивается на 3 раздельных части:
    1. initClassDefaults ( UIDefaults table )
      Именно этот метод мы переопределили в MyLookAndFeel для «подмены» UI класса кнопки и именно его стоит переопределять для этого дела.

    2. initSystemColorDefaults ( UIDefaults table )
      В этом методе определяются стандартные цвета различных элементых (как ни странно — hex-значениями).

    3. initComponentDefaults ( UIDefaults table )
      В этом же самом крупном из всех методе определяются шрифты, цвета, горячие клавиши, бордеры и прочие мелочи каждого отдельного компонента, если это необходимо.


Пожалуй теперь Вы достаточно узнали, чтобы преступить к первому UI-классу…

MyButtonUI.java


Как я уже писал выше — мы будем наследовать наш UI-класс от BasicButtonUI. Для начала накидаем простенький интерфейс для кнопки и убедимся, что всё работает именно так, как мы думаем:
public class MyButtonUI extends BasicButtonUI
{
    public void installUI ( JComponent c )
    {
        // Обязательно оставляем установку UI, реализованную в Basic UI классе
        super.installUI ( c );

        // Устанавливаем желаемые настройки JButton'а
        // Для абстракции используем AbstractButton, так как в нём есть всё необходимое нам
        AbstractButton button = ( AbstractButton ) c;
        button.setOpaque ( false );
        button.setFocusable ( true );
        button.setMargin ( new Insets ( 0, 0, 0, 0 ) );
        button.setBorder ( BorderFactory.createEmptyBorder ( 4, 4, 4, 4 ) );
    }

    public void paint ( Graphics g, JComponent c )
    {
        Graphics2D g2d = ( Graphics2D ) g;
        g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );

        AbstractButton button = ( AbstractButton ) c;
        ButtonModel buttonModel = button.getModel ();

        // Формой кнопки будет закруглённый прямоугольник

        // Фон кнопки
        g2d.setPaint ( new GradientPaint ( 0, 0, Color.WHITE, 0, c.getHeight (),
                new Color ( 200, 200, 200 ) ) );
        // Закгругление необходимо делать больше, чем при отрисовке формы,
        // иначе светлый фон будет просвечивать по краям
        g2d.fillRoundRect ( 0, 0, c.getWidth (), c.getHeight (), 8, 8 );

        // Бордер кнопки
        g2d.setPaint ( Color.GRAY );
        // Важно помнить, что форму необходимо делать на 1px меньше, чем ширина/высота компонента,
        // иначе правый и нижний края фигуры вылезут за границу компонента и не будут видны
        // К заливке это не относится, так как последняя колонка/строка пикселей игнорируется при заполнении
        g2d.drawRoundRect ( 0, 0, c.getWidth () - 1, c.getHeight () - 1, 6, 6 );

        // Сдвиг отрисовки при нажатой кнопке
        if ( buttonModel.isPressed () )
        {
            g2d.translate ( 1, 1 );
        }

        // Отрисовка текста и иконки изображения
        super.paint ( g, c );
    }
}
Теперь необходимо MyButtonUI использовать в MyLookAndFeel, чтобы он автоматически устанавливался всем кнопкам. Для этого в MyLookAndFeel классе наследуем метод initClassDefaults ( UIDefaults table ) и переопределяем необходимое значение:
protected void initClassDefaults ( UIDefaults table )
{
    // По прежнему оставляем дефолтную инициализацию, так как мы пока что не реализовали все
    // различные UI-классы для J-компонентов
    super.initClassDefaults ( table );

    // А вот, собственно, самое важное
    table.put ( "ButtonUI", MyButtonUI.class.getCanonicalName () );
}
Теперь запускаем наш старый пример и готовимся увидеть нашу «обновлённую» кнопку:


Хм, что-то пошло не так… Неужели мы где-то накосячили в UI-классе?
Попробуем напрямую задать UI кнопке:
final JButton jButton = new JButton ( "JButton" );
jButton.setUI ( new MyButtonUI () );
add ( jButton );
Пробуем:

Нет, с UI всё в порядке. Что же не так?

Именно над этим моментом я бился, наверное, целый день. В итоге, конечно же, всё оказалось до банального просто — мы упустили ещё один метод, который необходимо обязательно описать в первую очередь в нашем MyButtonUI классе — createUI ( JComponent c ). Как же я его упустил спросите Вы? Очень просто — иногда бывает черезчур вредно и недальновидно полагаться на IDE — можно упустить важные моменты из виду (я ни в коем случае не упрекаю IDE ибо виной тому моя невнимательность).

Данным метод присутствует в ComponentUI, но он… public static!? Спрашивается зачем в абстрактном ComponentUI статичный метод, который нельзя переопределить (собственно поэтому я и не увидел его сразу)?

Дело в том, что данный метод вызывается при создании UI в UIDefaults классе через Reflection, поэтому в нашем случае, так как мы этот метод не создали, при вызове был использован первый попавшийся под руку — его реализация в BasicButtonUI. А она, если посмотреть в исходный код, выглядит вот так:
public static ComponentUI createUI(JComponent c) {
    AppContext appContext = AppContext.getAppContext();
    BasicButtonUI buttonUI =
            (BasicButtonUI) appContext.get(BASIC_BUTTON_UI_KEY);
    if (buttonUI == null) {
        buttonUI = new BasicButtonUI();
        appContext.put(BASIC_BUTTON_UI_KEY, buttonUI);
    }
    return buttonUI;
}
Собственно, вот и причина, почему наш MyButtonUI не был использован. Исправим эту оплошность и добавим createUI ( JComponent c ) метод в MyButtonUI:
public static ComponentUI createUI ( JComponent c )
{
    // Создаём инстанс нашего UI
    return new MyButtonUI ();
}
Запустим наш пример ещё раз, но уже без насильной установки UI JButton'у:

Ура, работает!

Tip: Если один и тот же Ваш UI класс может быть использован для всех компонентов определённого типа, то стоит делать его. В нашем случае с MyButtonUI это также возможно, поэтому немного изменим приведённый выше метод:
private static MyButtonUI instance = null;

public static ComponentUI createUI ( JComponent c )
{
    // Создаём инстанс нашего UI
    if ( instance == null )
    {
        instance = new MyButtonUI ();
    }
    return instance;
}
При таком использовании UI мы сможем сэкономить, как на востребованной памяти, так и на излишних инстансах MyButtonUI класса, создаваемых для каждого отдельного JButton'а. Впрочем если Вы хотите добавить возможность стилизации UI каждого компонента отдельно — данный вариант не подойдёт.

Ложка дёгтя


Теперь мы знаем как создавать и устанавливать в LookAndFeel'е свой собственный UI-класс для кнопки, но что насчёт других компонентов? Всё то же, всё там же — необходимо просто дополнить метод initClassDefaults ( UIDefaults table ) необходимыми нам UI-классами. В финальном виде он должен выглядеть примерно так:
protected void initClassDefaults ( UIDefaults table )
{
    // Label
    table.put ( "LabelUI", ... );
    table.put ( "ToolTipUI", ... );

    // Button
    table.put ( "ButtonUI", ... );
    table.put ( "ToggleButtonUI", ... );
    table.put ( "CheckBoxUI", ... );
    table.put ( "RadioButtonUI", ... );

    // Menu
    table.put ( "MenuBarUI", ... );
    table.put ( "MenuUI", ... );
    table.put ( "PopupMenuUI", ... );
    table.put ( "MenuItemUI", ... );
    table.put ( "CheckBoxMenuItemUI", ... );
    table.put ( "RadioButtonMenuItemUI", ... );
    table.put ( "PopupMenuSeparatorUI", ... );

    // Separator
    table.put ( "SeparatorUI", ... );

    // Scroll
    table.put ( "ScrollBarUI", ... );
    table.put ( "ScrollPaneUI", ... );

    // Text
    table.put ( "TextFieldUI", ... );
    table.put ( "PasswordFieldUI", ... );
    table.put ( "FormattedTextFieldUI", ... );
    table.put ( "TextAreaUI", ... );
    table.put ( "EditorPaneUI", ... );
    table.put ( "TextPaneUI", ... );

    // Toolbar
    table.put ( "ToolBarUI", ... );
    table.put ( "ToolBarSeparatorUI", ... );

    // Table
    table.put ( "TableUI", ... );
    table.put ( "TableHeaderUI", ... );

    // Chooser
    table.put ( "ColorChooserUI", ... );
    table.put ( "FileChooserUI", ... );

    // Container
    table.put ( "PanelUI", ... );
    table.put ( "ViewportUI", ... );
    table.put ( "RootPaneUI", ... );
    table.put ( "TabbedPaneUI", ... );
    table.put ( "SplitPaneUI", ... );

    // Complex components
    table.put ( "ProgressBarUI", ... );
    table.put ( "SliderUI", ... );
    table.put ( "SpinnerUI", ... );
    table.put ( "TreeUI", ... );
    table.put ( "ListUI", ... );
    table.put ( "ComboBoxUI", ... );

    // Desktop pane
    table.put ( "DesktopPaneUI", ... );
    table.put ( "DesktopIconUI", ... );
    table.put ( "InternalFrameUI", ... );

    // Option pane
    table.put ( "OptionPaneUI", ... );
}
Понятное дело, на месте каждого «…» должен быть путь к конкретному UI-классу, определяющему внешний вид того или иного J-компонента.

Теперь Вы можете хотя бы приблизительно представить, сколько работы (и времени) потребует полное переопределение и реализация всех UI для стандартных Swing'овых компонентов.

Но не всё так плохо — если Вы исходно определились с концепцией/внешним видом Вашего LookAndFeel'а — дело пойдёт намного быстрее. Как я убедился на своём опыте — реализация большинства UI сводится к использованию нескольких стандартных методов, которые можно абстрагировать, вынести в отдельные утилит-классы и использовать повсеместно (к примеру отрисовка бордеров, фона, создание стандартных фигур и т.п.). Требуется лишь немного «магии» в некоторых сложных компонентах для доведения до ума их внешнего вида.

Итак, продолжим наше знакомство с различными UI классами, а точнее — рассмотрим предлагаемые BasicUI-классами методы и возможности, а также некоторые тонкости и сложные моменты, связанные с каждым отдельным UI и требующие той самой «магии».

Пожалуй для порядка пойдём прямо по списку, приведённому в коде выше…

Текстовые компоненты


BasicLabelUI – JLabel

Один из самых простых компонентов. В большинстве LookAndFeel'ов LabelUI либо вовсе не переопределён и используется BasicLabelUI, либо он лишь немного видоизменён. Например в WindowsLabelUI иначе работает отрисовка мнемоник-подчёркивания — оно видно только при зажатии Alt.

Всё что необходимо — отрисовка обычного или же HTML текста, а также мнемоник-подчёркивания — есть в BasicLabelUI.

Основная хитрость, кстати, в корректной отрисовке текста — для неё активно используются методы из SwingUtilities/SwingUtilities2. Сперва определяется расположение текста, а затем используется стандартный метод для его отрисовки :
SwingUtilities2.drawStringUnderlineCharAt ( l, g, s, mnemIndex, textX, textY );
Фактически — основную работу выполняет класс SwingUtilities2.

Если Вам интересно узнать больше — можете досконально изучить BasicLabelUI класс. Если Вы уже вполне хорошо ориентируетесь в графике никаких проблем возникнуть не должно.

Не будем задерживаться и пойдём дальше.

BasicToolTipUI – JToolTip

Не менее простой чем JLabel компонент. Фактически JToolTip (говоря абстрактно) это текст на фоне и с неким бордером.

Впрочем есть один небольшой нюанс — данный компонент при отображении располагается либо на JLayeredPane окна, в котором был показан тултип, либо на отдельном JWindow, если тултип выходит за рамки окна. Этот нюанс стоит учесть при отрисовке и избегать каких-либо специфичных форм тултипа и придерживаться «более-менее» прямоугольной.

Также если самому JToolTip'у задать setOpaque ( false ) и он будет находиться в рамках текущего окна, то он будет действительно прозрачен. В случае же отображения тултипа в отдельном JWindow этот трюк уже не сработает.

Пока что для выхода из этой ситуации есть несколько трюков — делать окно прозрачным недавно появившимися возможностями (AWTUtilities в версиях 1.6 u10 + или же методы Window в 1.7+) или же делать «скриншот» области, над которой показывается тултип и помещать его в качестве фона.

У первого варианта минус в том, что данная фича не везде работает корректно. У второго минус в статичности и скорости работы, т. е. если под окном тултипа была какая-либо анимация — её отсутствие будет заметно. Впрочем, в контексте тултипа это вряд ли играет большую роль.

Кнопки


BasicButtonUI – JButton

В примере MyButtonUI, приведённом выше, я не использовал методы определённые в BasicButtonUI, а просто полностью переопределил метод paint ( Graphics g, JComponent c ).

Делать так или же наследовать методы:
paintButtonPressed ( Graphics g, AbstractButton b )
paintText ( Graphics g, AbstractButton b, Rectangle textRect, String text )
paintIcon ( Graphics g, JComponent c, Rectangle iconRect )
paintFocus ( Graphics g, AbstractButton b, Rectangle viewRect, Rectangle textRect, Rectangle iconRect )

дело сугубо Ваше и зависит, скорее, от сложности отрисовываемой кнопки.

При желании можно и вовсе разделить отрисовку на части своими методами. К примеру вынести в отдельные методы отрисовку фона и текста с иконкой, но не более.

В любом случае — результат будет один и тот же, если нигде не ошибиться конечно же.

BasicToggleButtonUI – JToggleButton

Для JToggleButton нет ToggleButtonUI класса — только BasicToggleButtonUI, наследуемый от BasicButtonUI и фактически разницы между ними почти никакой нет. Только что в JToggleButton модель кнопки может принимать состояние selected=true, а в JButton нет, хотя сама модель в обоих случаях одна и та же.

Соответственно и визуальное различие заключается лишь в том, что JToggleButton может оставаться в pressed состоянии без удержания компонента мышью. Ни отрисовка, ни логика при этом фактически никак не меняется.

Поэтому мне кажется логичным реализовать UI как JButton так и JToggleButton одним и тем же классом или же просто пронаследовать для визуальной чистоты (MyToggleButtonUI extends MyButtonUI), ну и конечно же не забыть добавить статичный метод createUI ( JComponent c ) в MyToggleButtonUI.

BasicRadioButtonUI – JRadioButton

Аналогично JToggleButton – у данного компонента нет RadioButtonUI класса. BasicRadioButtonUI же унаследован от BasicToggleButtonUI и отличается лишь отрисовываемой иконкой и отсутствием стандартного фона.

BasicCheckBoxUI – JCheckBox

Данный UI полностью дублирует BasicRadioButtonUI, за исключением иконки.

Меню


BasicMenuBarUI — JMenuBar

Данный компонент представлет из себя «подставку» под JMenu компоненты и сам по себе кроме фона ничего не отрисовывает.

Впрочем при переопределении BasicMenuBarUI стоит также переопределить и бордер компонента, отсающийся «по наследию».

Ещё одна особенность — на Mac OS оформление и сам UI, а также все остальные UI меню и элементов меню не будут иметь смысла, если используется интеграция с системным меню-баром.
т. е. при использовании:
System.setProperty ( "apple.laf.useScreenMenuBar", "true" );
при запуске на Mac OS.

BasicMenuItemUI — JMenuItem

Честно признаюсь — этот компонент самый мерзкий из всех по реализации и количеству геморроя. Поэтому я более подробно обозначу все проблемные места.

В BasicMenuItemUI есть несколько исходно разнесённых методов:
  1. paintMenuItem ( Graphics g, JComponent c, Icon checkIcon, Icon arrowIcon, Color background, Color foreground, int defaultTextIconGap )
    Основной метод, собирающий воедино отрисовку разных частей элемента меню.

  2. paintBackground ( Graphics g, JMenuItem menuItem, Color bgColor )
    Отрисовывает фон элемента меню.

  3. paintCheckIcon( Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr, Color holdc, Color foreground )
    Отрисовывает иконку выбора у JRadioButtonMenuItem или JCheckBoxMenuItem.

  4. paintIcon( Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr, Color holdc )
    Отрисовывает иконку, предоставляемую JMenuItem'ом или JMenu.

  5. paintText( Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr )
    Отрисовывает текст элемента меню.

  6. paintAccText( Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr )
    Отрисовывает текст хоткея, повешенного на данный элемент меню (например «Alt+F4»).

  7. paintArrowIcon( Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr, Color foreground )
    Отрисовывает стрелку, указывающую на подменю в JMenu.


Что самое интересное — половина из этих вещей не нужна в обычном JMenuItem. Но, забегая вперёд, скажу что данный класс реализует визуальное представление всех четырёх различных элементов меню — JMenu, JMenuItem, JCheckBoxMenuItem, JRadioButtonMenuItem.

И это только начало всей каши, дальше — лучше.

Все вычисления размеров и расположений отдельных элементов вынесено в класс MenuItemLayoutHelper, который вообще находится в другом пакете (sun.swing) и невозможно никак изменить. Также данный класс не входит в некоторые старые сборки JDK и OpenJDK.

В итоге, если Вас так или иначе не устраивает расположение элементов или их размеры — приходится прибегать к исхищрениям и уловкам и всячески обходить стандартные данные MenuItemLayoutHelper'а (к примеру мне нужно было сделать выравнивание всех элементов меню в линию с пробелом в иконку 16х16, даже если ни у одного элемента она не указана).

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

BasicMenuUI – JMenu

Данный UI 1 в 1 повторяет реализацию BasicMenuItemUI, но Вам придётся выбрать меньшую из зол — либо наследовать свою реализацию BasicMenuItemUI и копировать логику работы JMenu из BasicMenuUI, либо наследовать BasicMenuUI и копировать визуальную реализацию из Вашего наследника BasicMenuItemUI.

Я выбрал второй вариант, так как всё же проще разбираться в своём коде, да и поддерживать любые изменения логики будет куда сложнее, нежели дублировать визуальное оформление.

BasicCheckBoxMenuItemUI – JCheckBoxMenuItem
BasicRadioButtonMenuItemUI – JRadioButtonMenuItem

Оба этих UI класса можно без зазрений совести расширять от Вашей реализации BasicMenuItemUI и переопределить лишь метод отрисовки выбора чекбокса/радиокнопки.

А можно вовсе реализовать эту отрисовку прямо в Вашем наследнике BasicMenuItemUI, а два данных UI класса просто расширить от него, добавив лишь статичный createUI ( JComponent c ) метод.

BasicPopupMenuSeparatorUI – JSeparator

Данный класс представляет лишь немного видоизменённый BasicSeparatorUI и используется исключительно в JPopupMenu для разделения элементов меню. Сам компонент JSeparator представляет из себя простую отрисованную полосу/линию/черту и используется для визуального разделения логических частей интерфейса.

При переопределении данного UI класса достаточно просто изменить стандартный метод paint ( Graphics g, JComponent c ), отрисовав в нём желаемый вид разделителя.

BasicPopupMenuUI – JPopupMenu

Собственно, последний компонент из серии меню — JPopupMenu. Вся необходимая для работы данного компонента логика реализована в BasicPopupMenuUI, поэтому нам лишь остаётся изменить внешний вид на свой вкус.

Собственно здесь два момента — бордер меню, который определит отступ от края попап-меню до внутренних элементов и отрисовка фона меню.

Бордер стоит установить в методе installUI ( JComponent c ), а отрисовку, как обычно, выполнить в методе paint ( Graphics g, JComponent c ).

Также, если Вам хочется экзотики, можно реализовать отрисовку выделения элементов попап-меню на его фоне, а не внутри каждого отдельного элемента меню. Это может быть полезно в случае если Вам необходим некоторый отступ от края до элементов, но выделение Вы хотите рисовать на всю ширину меню вплоть до самого края (такое выделение можно наблюдать, к примеру, в системных попап-меню на Mac OS).

Для этого правда придётся дополнительно в UI-классе запомнить текущее попап-меню и при отрисовке фона узнавать состояние элементов, а также корректно прослушивать смену выделения для перерисовки фона.

Продолжение следует...


Через некоторое время я опубликую II часть, в которой подробно опишу все оставшиеся BasicUI-классы, а также приведу несколько готовых примеров реализации UI компонентов.

Если Вы уже прочитали и освоили весь приведённый материал — можете попробовать приступить к реализации своего LaF'а или же просто попробовать переопределить несколько стандартных UI-классов.

Также Вы можете покопаться в исходных кодах WebLookAndFeel библиотеки, над которой я работаю в данный момент и на основе которой я смог достаточно основательно попрактиковаться в написании UI. Там Вы сможете найти полноценную реализацию всех стандартных UI, большинство которых я описал или ещё опишу в этой и следующей статьях, а также некоторые другие интерфейсные «плюшки».

Более подробно о ней, а также оставшихся UI классах читайте в продолжении примерно через неделю. И спасибо за внимание!

Если у Вас есть какие-либо вопросы по Java LookAndFeel/UI — не стесняйтейсь и отписывайтесь в комментариях! Постараюсь ответить на них по мере возможностей.

P.S. Отдельное спасибо проекту dumpz.org за приятную читабельную подсветку кода.
Метки:
ALEE Software 40,61
ПО для электронных архивов и библиотек, оцифровка
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Похожие публикации
Комментарии 40
  • +1
    Пишите еще!

    И скоро каждый из читателей напишет свой лукандфил для своих рабочих проектов. Так сказать с блекджеком :) Сарказм конечно. Врага нужно знать в лицо.
    • +1
      Ну напишет или нет — дело самого читателя :)

      Но более-менее адекватно описать «что и как» я всё-таки постараюсь, ибо подобного описания я толком найти нигде не смог.

      Кстати, я был бы только ЗА, если бы появилось больше желающих развивать Swing-направление (в частности — написанием большего количества LaF, пусть даже коммерческих), так как оно сейчас, мягко говоря, отошло на второй план у Oracle, что весьма печально.
    • +2
      Раз уж Нимбус стал дефолтным с седьмой джавы и уже давно есть в шестой, то логичнее новые проекты по созданию LaF начинать все-таки с простого переопределения пэйнтеров в Нимбусе. Там большинство работы уже сделано за вас.
      • +3
        Тут есть пара моментов, из-за которых этого делать, как мне кажется, не стоит…

        Во-первых — 7ое JDK очень долго будет входить в полноценное использование.

        Во-вторых — Вы видели те самые пейнтеры? Я вот видел — волосы дыбом встают.
        Такое ощущение, что этот код писали далеко не руками (может это и так, ибо были упоминания о том, что есть некий NimbusDesigner?). Учитывая это — переопределение пейнтеров нимбуса займёт ещё больше времени и будет гораздо менее читаемо и изменяемо, в отличии от описанного мной способа.
        Если не верите, могу написать и показать реализацию какого-либо компонента из NimbusLaF «по старинке» — к примеру взять прогресс-бар.

        Может, конечно, Nimbus на выходе будет немного более оптимален в своих отрисовках, но эту разницу можно также сгладить, поработав над кодом.

        В итоге лично я не вижу выигрыша в случае наследования Nimbus-пейнтеров (по крайней мере на данный момент). Конечно хотелось бы иметь что-то более человечное, но мне кажется это пока-что мечты…
        • 0
          Код нимбуса не ковырял, возможно он и сгенерирован.

          Дело в том что в UI классах кроме отрисовки еще куча логики содержится. Порой ее не хотелось бы трогать, или в BasicUI каких-либо фич из Нимбуса может не быть, а тут не нужно откатываться на BasicUI.

          А так да, на проекте пытался довести GUI до более-менее косистентного вида на Metal, Nimbus, GTK и Windows. Это адъ. Если Ваш WebLaF на мавене будет, и его буду рад потестить :)
          • +1
            Собственно — NimbusDesigner вроде как будет визуальным редактором для LaF/UI (правда я не представляют как они уместят туда всё необходимое), если его доведут таки до ума и откроют.

            Насчёт логики — я изучал часть исходных кодов Nimbus — могу заверить — там логики (именно логики работы, а не логики отрисовки компонента) не сильно больше чем в Basic UI классах. Именно поэтому я и предлагаю наследоватсья от них — большая часть необходимого уже есть там — остаётся только подверстать внешний вид.

            WebLaF, думаю, смогу выложить и на Maven (или только туда) — четно говоря с этим ещё не определился (какие плюсы-минусы?). А пока что проект (самая последняя версия исходников) хостится на нашем внутреннем SVN. Ранее собирался на Google code выложить, но теперь есть сомнения…
        • +1
          Если вы будете делать L&F на Synth (на котором основан Nimbus), то с удивлением узнаете, что в нем нельзя сделать выделенный (например, когда вы проводите над ним мышкой) задизейбленный пункт меню. Кроме этого есть еще и просто несколько багов, ограничивающих возможности задания свойств L&F. Эти баги не влияют на Nimbus, а потому возможно никогда не будут исправлены. При этом самому переопределить классы и исправить их нельзя — их нужно копировать (по крайней мере в java 6).
          • +1
            Честно говоря так глубоко в Synth/Nimbus не заглядывал, так как мне кажется сейчас впринципе невозможно на их основе что-то толковое написать (точнее возможно, но для этого нужно иметь целую команду, которая будет возиться со всеми мелочами и проблемами, иначе уйдёт вечность).

            Но заметка, однозначно, интересная.

            Инетересно, кстати, есть ли написанные под Nibus/Synth расширения/LaF (даже если коммерческие)?
            Множество тех LaF/UI что я видел использовали Basic/Component UI за базу.
            • +1
              Ну я в итоге LaF на основе Synth написал (то, что исправить нельзя, осталось не исправленным). Но на это ушло несколько месяцев. К этому нужно приплюсовать работу дизайнеров — не знаю сколько они его рисовали.

              Дошел ли он в итоге до реальных пользователей — не знаю, я перешел в другую компанию.
              • 0
                Ясно. Если брать текущее состояние WebLookAndFeel и затраченное непосредственно на него время — я бы сказал недели 3-4, не более. Включая все виды работ по библиотеке, естественно.

                В общем — не сказал бы что это очень много. А вот плюсы подобного решения уже вполне очевидны и ощутимы.
        • НЛО прилетело и опубликовало эту надпись здесь
          • +2
            Есть много проблем, связанных с нативным внешним видом приложений. Особенно если Ваше приложение немного (или намного) сложнее, чем просто «табличка-тулбарчик-двекнопки».

            Фактически весь «геморрой» делится на два направления:
            — Поддержание своего LaF, но с любыми желаемыми плюшками
            — Использование готовых/нативных и, естественно, никаких плюшек (либо изобретение велосипедов)

            Я уж не говорю о том, что для опрятного оформления приложения придётся (даже при использовании нативных LaF) городить системо-зависимые части, ибо стандартное расположение компонентов и их вид (включая настройки общие для всех систем) по гайдлайнам разных систем абсолютно разные.
            • +1
              Собственно, после двух лет «возьни» с нативным LaF я пришёл к выводу, что первый вариант, хоть и затратен на первых парах, но зато гораздо более удобен и приспосабливаем.

              Тем более Вы в любом момент простым движением руки можете поменять поведение того или иного компонента, что невозможно или весьма затратно для нативного LaF'а.
              Аналогично с оформлением — нативный LaF привязывает Вас к тому набору компонентов, который имеется в Swing или же реализован по образу и подобию LaF'а в сторонних библиотеках (таких, сразу скажу, мало), ибо всё остальное будет выглядеть глазовыдерающе на фоне нативного стиля.
            • +1
              А что скажете за Substance (Очень приятный LaF имхо)? Ковырялись в нем?
              Жаль что Кирилл забил болт на Substance…
              • +1
                Ковырялся конечно, но не так глубоко, как хотелось бы (а что собственно интересует?) :)
                А сейчас даже уже не найду исходников, да и выкачать толком неоткуда, чтобы изучить…

                Substance один из лучших реализованных LaF'ов. Исходно я даже рассматривал возможность его использования заместо написания своего LaF, но не удовлетворили некоторые визуальные реализации компонентов и некоторые другие мелочи. Ну и, собственно, что самое главное — поддержка/живость проекта — было бы очень неприятно отказываться от него, если бы на очередном обновлении JDK что-то слетело.

                Всего я знаю 3 достойных LaF:
                Alloy — он, вроде как, ещё продаётся, но не обновлялся века
                Synthetica — этот тоже коммерческий, но всё ещё живой
                И, сосбтвенно, Substance, который, к сожалению, более не поддерживается

                Есть ещё несколько неплохих LaF'ов, но по оформлению/возможностям они сильно уступают этим трём.
                Можете вот тут глянуть разные LaF'ы, если ещё не видели ресурс: www.javootoo.com
                • +1
                  Кирилл выложил все исходники на гитхабе.

                  Он там внутрях своего LaF-а замутил всякие анимации и прочие вкусности.
                  А вместо субстанца теперь есть его форк и делает его Danno Ferrin
                  • +1
                    Да, форк вот только что нашёл в дебрях сети.
                    А насчёт анимации — честно говоря пока не смотрел.

                    Имхо в большинстве случаев анимацию компонентов лучше либо реализовывать оптимальными средствами прямо внутри UI, либо вообще забыть об этом. Всё таки LaF может сильно повлиять на скорость работы приложения, если будет сильно перегружен.

                    Я, конечно, поигрался с анимацией в паре мест, не буду лукавить, но постарался реализовать её максимально безопасно. Впрочем, ещё посмотрю что там есть на эту тему :)

                    А за ссылку на гитхаб спасибо, как дойдут руки — покопаюсь.
              • 0
                И про JTatoo пару слов. Ковырялся в нем однажды и понял что он раза в три медленнее Metal работает, потому как градиенты рисует.
                • +1
                  Не думаю всё же что градиенты — корень зла, хотя сам и не видел что там внутри творится.
                  При корректном использовании отрисовка градиентов не создаёт практически никакой нагрузки.

                  Кстати, даже если JTatoo или ещё какой LaF работает медленнее (точнее сказать, медленее отрисовывает те или иные элементы) — это не повод отказываться от него. Более сложное и опрятное оформление так или иначе потребует большего количества кода и сложности отрисовки, что неизбежно влечёт замедление отрисовки — с этим ничего не поделать. Но пока отрисовка отрабатывает в адекватные промежутки времени и не вызывается зазря — имхо проблем использовать LaF быть не должно.
                  • 0
                    Можете чтонибудь про методы измерения производительности (performance) LaF'ов рассказать? Может есть какие бенчмарки? Да и вообще как можно в свинге/awt чтонибудь померить :)
                    • +1
                      Это весьма абстрактная тема — точно так же как попросить методы измерения производительности некоторого приложения X. Каждый LaF реализован абсолютно по-своему (кто на что горазд, да), и, соответственно, сложно подвести всё их многообразие под одну черту.

                      Собственно известных мне средств, заточенных под тестирование производительности Swing интерфейса, нет. Может таковые и существуют, но я лично в этом сомневаюсь.

                      Если рассматривать частные случаи — можно измерять время отрисовки тех или иных компонентов (непосредственно скорость отработки метода paintComponent при том или ином LaF) вручную проставляя «замерщики» в компонентах или прямо в UI классах, если имеется исходный код. Я делаю именно так при оптимизации неудачных частей интерфейса и улучшаю/изменяю алгоритм и отрисовку до тех пор, пока компонент не будет съедать, менее 1-2 мс на полную отрисовку.

                      Даже не знаю, что ещё можно было бы посоветовать для этого дела…
                      • 0
                        Я могу ошибаться, но мне кажется что скорость отрисовки зависит от 3D ускорителя и активирована ли в jvm отрисовка интерфейса через 3D акселератор. Какие бы графические навороты не использовались бы при отрисовки интерфейса то при включенной функции ускорения интереса через 3D, она будет отрисованна мгновенно. В 7ке эта функция включена по умолчанию, в 6ке её кажется нужно активировать ключами. Правда она появилась не сразу а после какого-то апдейта.
                        • +1
                          Скорость отрисовки, естественно, зависит от различных параметров системы, но…

                          «Какие бы графические навороты не использовались бы при отрисовки интерфейса то при включенной функции ускорения интереса через 3D, она будет отрисованна мгновенно»

                          Мгновенно ничего не бывает, увы. Действительно, при использовании подобных средств скорости могут взлетать на 5-10 порядков, но всегда можно наворотить такого, что даже на самом новом железе будет тормозить. И дело там будет совсем не в том, что системе не хватает средств на отрисовку.

                          Любая графическая или алгоритмическая операция, выполняемая при отрисовке элементов занимает некоторое время, путь это будут даже наносекунды. При наличии огромного количества подобных операций время складывается и выходят вполне значимые величины, ведь в Swing вся отрисовка интерфейса происходит в одном потоке, в отличие, скажем, от нативных виндовых компонентов. Именно поэтому в Swing есть множество оптимизаций, появившихся, если мне не изменяет память, в версии 1.4, когда, тогда ещё, Sun взялся всерьёз за проблему скорости работы интерфейса (и результаты были внушительные).
                          • +1
                            но я все же с интересом смотрю на javafx. В сивингах они сделали все что можно было сделать. Особенности кросплатформности накладывают ограничения, но не сильные, а преимущества огромные. Но все же они развиваются дальше и пытаются привнести стиль веб программирования на десктопы. Это тоже интересно.
                            • +1
                              В свинге они сделали далеко не всё, что можно было бы, просто кому-то явно приспичело реализовать новый проект и тянуть его заместо Swing — не более. Я не знаю чем конкретно они руководствовались при выборе, но мне он кажется весьма сомнительным.

                              Анимацию, различные эффекты и прочий сахар, который добавляет JavaFX — давно уже реализован так или иначе в сторонних библиотеках для Swing — где-то более оптимально, где-то менее. Впрочем это не мешает интегрировать схожие адекватные средства в Swing, нежели открывать отдельную интерфейсную ветку.
                • +1
                  Эх, эту бы статью, да года три назад…
                  • +1
                    Самому бы хотелось в своё время найти подобный «мануал» и не мучаться над неясными мелочами и ошибками, но что поделать…
                    • 0
                      Помню, как шерстил код и матерился на полное отсутствие документации именно по этому вопросу. Были некоторые сведения в книгах, но не помогало. Так и бросил java, не сделав желаемый интерфейс. Перешел в веб-программирование.
                      • +1
                        Хех, очень давно тоже пытался начать с нуля, но бросил по той же причине. В итоге через пару лет опять вернулся к необходимости заняться этим делом…

                        Собственно, за пару лет удалось долгим путём проб и ошибок пробиться к желаемому результату :)

                        Но действительно поражает, что до сих пор не вышло ни одного толкового описания на эту тему. Максимум статьи по работе с графикой и не более.
                        • 0
                          Я до сих пор встречаю программы, которые пишут на AWT. У swing большая проблема с производительностью. Тот же swt или awt хоть и сложнее, и имеют меньше возможностей, но работают на порядок быстрее. Да и парадигма о том, что системный laf единственно правоверный до сих пор доминирует в умах людей.
                          • +1
                            Честно говоря, впервые слышу про проблемы производительности в Swing — он как раз таки был оптимизирован и заточен для большей производительности. Другой вопрос, что неумеючи можно такого наворотить… Видимо оттуда сплетни и растут.

                            Насчёт парадигмы — даже многие Mac'овые приложения уже давно используют свою стилизацию, которая ничем не хуже или даже лучше подходит к приложению. Виндовые так и вовсе — кто во что горазд. Так что мне кажется, что скоро останутся лишь те, кто любит только повозмущаться насчёт «несистемности» приложения, но реальных доводов «против» там не будет.
                  • +2
                    Спасибо за статью, полезно, жду продолжения. И вообще интересна тема создания своих новых элементов управления
                    • +1
                      Всегда пожалуйста :)
                      Насчёт создания новых элементов — думаю это будет во 2ой, а может даже 3ей части.
                      Я имею в виду именно создание стилизуемого посредством UI компонента, конечно же.
                      • 0
                        По javafx не будет подобных обзоров? Тем более, что они обещают тесную интеграцию со swing.
                        • +1
                          Думаю что будут, но не в ближайший месяц.

                          Я пока только начинаю копаться с JavaFX и мне направление, пока что, кажется весьма сомнительным. Там действительно есть несколько моментов, которых в Swing сложно или невозможно реализовать, но они не настолько критичны, чтобы соскочить с места и перепрыгнуть в «лагерь» JavaFX. Да и косяков там немеряно на данный момент — иногда вообще не ясно ты ли накосячил или нет.
                          • 0
                            Да и, кстати, тесную интеграцию пока-что только обещают…
                            Как только она действительно появится (я не говорю о разных затычках и хаках) — будет реальный повод более подробно описать что и как работает/взаимодействует и какие плюсы можно получить от подобной интеграции.
                      • 0
                        Уважаемый автор, почему хабр показывает мне source code вместе c html что с этим можно сделать? Скрин:
                        image
                        Помогите пожалуйста
                        • 0
                          Ранее нормальное форматирование кода можно было сделать только таким способом, сейчас уже он не работает, поэтому и вылезает HTML код. Я уже обещал подправить исходники в статьях, но пока у меня просто не хватает на это времени. Как только появится возможность — сразу же подправлю.
                          • 0
                            Жду с нетерпением.
                            • 0
                              А пока простой виджет:
                              Виджета не вставишь. Значит просто код. Копируйте в консоль и запускайте!
                              (function(){var xx=document.getElementsByTagName('code');for(var x in xx){x=xx[x];x.innerHTML=x.innerHTML.replace(/\n/g,'<br>');x.innerHTML=x.innerText;}}());
                              

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое