ПО для электронных архивов и библиотек, оцифровка
42,41
рейтинг
18 апреля 2011 в 14:57

Разработка → Улучшаем интерфейс Java-приложения tutorial

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

Достаточно много различной раздробленной информации существует на тему работы со Swing и графикой в просторах интернета, а также на тему интерфейсов Java-приложений. Кто-то твердит о том, что Java морально устарела и десктоп-приложения на Java не имеет смысла писать, кто-то с пеной у рта доказывает обратное. В то же время работа идет, приложения пишутся и встают очередные проблемы. В предыдущей статье я уже привел небольшой список полезных библиотек для исключительных случаев, но нередко бывает так, что никакая сторонняя библиотека не позволяет сделать то, что Вам нужно. Именно в такой момент стоит задуматься о возможной необходимости написания своих компонентов.

Итак, в данном посте я постарался изложить самые важные и значимые на мой взгляд моменты по работе со Swing и графикой — как создавать компоненты, как стилизовать интерфейс, чего делать не стоит и многое другое…


Витая в облаках


Думаю, что многих удерживает от изучения или разработки на Java вопросы «как сделать оформление своего приложения ярким и запоминающимся?» и «разве это возможно на Java?» и увы, действительно практически нет ни одного крупного ресурса по данной теме, которые бы полностью или хотя бы частично пролили свет на ситуацию.

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

Что же мы имеем?


Пожалуй стоит начать с основ, оттолкнувшись от которых можно будет обсудить «более высокие материи».

Если Вам достаточно хорошо известны такие вещи как Swing и LaF и как они устроены, предлагаю не тратить времени зря и сразу перейти к следующей главе топика.

Итак, построение интерфейса Java-приложения основывается на библиотеке Swing, которая содержит в себе все базовые компоненты, которые возможно встретить в различных ОС. Они могут выглядеть по разному, иметь немного различные функциональные особенности (на различных ОС), но по сути — они выполняют одно и то же предназначение. Это такие компоненты как кнопки (JButton), текстовые поля (JTextField, JPasswordField), лэйблы (JLabel), текстовые области (JTextArea, JEditorPane), скроллы (JScrollBar, JScrollPane) и пр.

Swing в «базовой комплектации» позволяет использовать несколько разных LaF'ов (Look and Feel, можно также назвать их «скинами» к Swing) для изменения внешнего вида всех компонентов Вашего приложения «разом» — Metal LaF (стандартный Java-стиль), Nimbus LaF (просто отдельный специфичный стиль), System LaF (стиль Вашей ОС). Впрочем есть много отдельно дописанных LaF'ов оформленных в полноценные библиотеки, которые можно легко и быстро использовать в любом проекте.
У каждого отдельного компонента есть возможность задания UI-класса, который определяет как он будет отрисован и иногда — какую функциональность он будет иметь, всё зависит от реализации. Непосредственно LaF определяет UI сразу для всего дерева компонентов, доступных в Вашем приложении. Вы можете как использовать LaF, так и менять стиль отдельных компонентов задавая им UI. Также можно выставить определенный LaF (системный, например) и затем, при необходимости, изменить UI отдельных элементов.

Приведу несколько ресурсов с различными кастомными LaF'ами, которые можно быстро приспособить для использования:

Отдельные наиболее известные и проработанные стили

Tiny LaF — аналог классического стиля Windows XP


Quaqua LaF — аналог Mac OS X интерфейса с множеством возможностей по кастомизации компонентов


Substance LaF


Synthetica LaF


Alloy LaF
image

JGoodies


Несколько наборов стилей

Набор #1 — сайт посвященный различным известным LaF для Java-приложений
Набор #2 — небольшая статья со списком разных LaF
Набор #3 – еще одна статья на тему LaF

Важно также отметить, что не все LaF'ы могут быть использованы под любой ОС. Некоторые специализируются на определенных ОС, как к примеру Quaqua LaF. Его, конечно, возможно запустить под Windows или Linux, и на первый взгляд проблем не возникнет, но некоторые компоненты могут вызывать ошибки при работе, вплоть до падения JVM. Некоторые другие стили могут и вовсе не запуститься на не предназначенной для использования ОС. Некоторые же стили запрещено использовать на определенных ОС по лицензионному соглашению.

Сторонние наработки


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

Поэтому перед тем как переходить к описанию собственноручного написания/изменения элементов интерфейса, я считаю логичным привести доступные, поддерживаемые и достаточно популярные библиотеки. Итак, посмотрим какие наработки существуют в этой области (советую для ознакомления пройти на указанные сайты):

SWT (сайт) (мануал/доки) (примеры) (скачать)
Лицензия: EPL
Поддерживаемые платформы: Windows/Linux/MacOSX/Solaris and some others
SWT представляет собой обертку для удобного использования нативных компонентов разных операционных систем. Напрямую совмещать компоненты Swing и SWT не удастся, но есть несколько библиотек, предназначенных для этого (например эта).

SwingX (сайт) (мануал/доки) (скачать)
Лицензия: LGPL 2.1
Поддерживаемые платформы: All java-supported platforms
Достаточно старая и известная библиотека с расширенным набором Swing-компонентов. На данный момент проект находится в некоем переходном состоянии.

Jide (сайт) (мануал/доки) (скачать демо)
Лицензия: Commercial/GPL with classpath exception
Поддерживаемые платформы: All java-supported platforms
Одна из лучших коммерческих библиотек, предоставляющая широчайший спектр всевозможных компонентов.

Дополнительно

Вероятно Вам могут пригодиться библиотеки упомянутые в моей предыдущей статье для разработки приложений с использованием более сложных элементов (flash/video и т.п.). Могу только добавить еще одну библиотеку, которую я также использовал в нескольких проектах — JavaLayer – она позволяет воспроизводить mp3 файлы и mp3-стримы. Она проста в использовании, не требует никаких нативных вещей, кроссплатформенна и не ест ресурсов машины.

Есть конечно еще некоторые другие библиотеки (как коммерческие, так и свободные), но они либо морально устарели, либо содержат очень мало полезных вещей. Итак, Вы уже посмотрели какие LaF можно использовать, какие есть разные готовые компоненты в различных библиотеках, но в итоге — Вы всё еще не удовлетворены — часть компонентов имеет не тот стиль, какой хотелось бы, библиотеки уже успели десять раз морально устареть, обнаруживаются фатальные ошибки внутри библиотек, поддержка дает слабину… Что же делать? Однозначно — взять одного свободного дизайнера и код в свои руки!..

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

Написание своего UI


Сперва уделю немного времени этому моменту, так как не всегда необходимы какие-то хитрые компоненты, а достаточно стилизованных стандартных. Именно в таких случаях стоит прибегнуть к написанию своего небольшого UI-класса к J-компоненту, тем более что ко всем стандартным Swing-компонентам без исключения имеются Basic-UI классы, которые позволят Вам быстро и без лишней мороки создать свою стилизацию для компонента. Приведу небольшой пример создания UI для JSlider.

Для начала создадим класс, переопределяющий BasicSliderUI, и опишем его внешний вид имеющимися изображениями:
public class MySliderUI extends BasicSliderUI
{
  public static final ImageIcon BG_LEFT_ICON =
      new ImageIcon ( MySliderUI.class.getResource ( "icons/bg_left.png" ) );
  public static final ImageIcon BG_MID_ICON =
      new ImageIcon ( MySliderUI.class.getResource ( "icons/bg_mid.png" ) );
  public static final ImageIcon BG_RIGHT_ICON =
      new ImageIcon ( MySliderUI.class.getResource ( "icons/bg_right.png" ) );
  public static final ImageIcon BG_FILL_ICON =
      new ImageIcon ( MySliderUI.class.getResource ( "icons/bg_fill.png" ) );
  public static final ImageIcon GRIPPER_ICON =
      new ImageIcon ( MySliderUI.class.getResource ( "icons/gripper.png" ) );
  public static final ImageIcon GRIPPER_PRESSED_ICON =
      new ImageIcon ( MySliderUI.class.getResource ( "icons/gripper_pressed.png" ) );

  public MySliderUI ( final JSlider b )
  {
    super ( b );

    // Для корректной перерисовки слайдера
    b.addChangeListener ( new ChangeListener()
    {
      public void stateChanged ( ChangeEvent e )
      {
        b.repaint ();
      }
    } );
    b.addMouseListener ( new MouseAdapter()
    {
      public void mousePressed ( MouseEvent e )
      {
        b.repaint ();
      }

      public void mouseReleased ( MouseEvent e )
      {
        b.repaint ();
      }
    } );
  }

  // Возвращаем новый размер гриппера
  protected Dimension getThumbSize ()
  {
    return new Dimension ( GRIPPER_ICON.getIconWidth (), GRIPPER_ICON.getIconHeight () );
  }

  // Отрисовываем сам гриппер в необходимом месте
  public void paintThumb ( Graphics g )
  {
    int positionX = thumbRect.x + thumbRect.width / 2;
    int positionY = thumbRect.y + thumbRect.height / 2;
    g.drawImage ( isDragging () ? GRIPPER_PRESSED_ICON.getImage () : GRIPPER_ICON.getImage (),
        positionX - GRIPPER_ICON.getIconWidth () / 2,
        positionY - GRIPPER_ICON.getIconHeight () / 2, null );
  }

  // Отрисовываем «путь» слайдера
  public void paintTrack ( Graphics g )
  {
    if ( slider.getOrientation () == JSlider.HORIZONTAL )
    {
      // Сам путь
      g.drawImage ( BG_LEFT_ICON.getImage (), trackRect.x,
          trackRect.y + trackRect.height / 2 - BG_LEFT_ICON.getIconHeight () / 2, null );
      g.drawImage ( BG_MID_ICON.getImage (), trackRect.x + BG_LEFT_ICON.getIconWidth (),
          trackRect.y + trackRect.height / 2 - BG_MID_ICON.getIconHeight () / 2,
          trackRect.width - BG_LEFT_ICON.getIconWidth () - BG_RIGHT_ICON.getIconWidth (),
          BG_MID_ICON.getIconHeight (), null );
      g.drawImage ( BG_RIGHT_ICON.getImage (),
          trackRect.x + trackRect.width - BG_RIGHT_ICON.getIconWidth (),
          trackRect.y + trackRect.height / 2 - BG_RIGHT_ICON.getIconHeight () / 2, null );

      // Заполнение участка пути слева от гриппера
      g.drawImage ( BG_FILL_ICON.getImage (), trackRect.x + 1,
          trackRect.y + trackRect.height / 2 - BG_FILL_ICON.getIconHeight () / 2,
          thumbRect.x + thumbRect.width / 2 - trackRect.x - BG_LEFT_ICON.getIconWidth (),
          BG_FILL_ICON.getIconHeight (), null );
    }
    else
    {
      // Для вертикального слайдера аналог приведен в приложенном jar'е с демкой и сурцами
    }
  }
}
Как можно заметить, все необходимые переменные для отрисовки присутствуют в Basic-UI классе и мы можем их напрямую использовать:
thumbRect – прямоугольник гриппера
trackRect — прямоугольник «пути» слайдера
На самом деле большую часть необходимых перерисовок вызывает сам Basic-UI класс и Вам не нужно об этом беспокоиться (например при нажатии на гриппер или его перетаскивании).

В этом UI я немного схитрил и не переопределял метод calculateTrackRect(), который определяет размеры «пути» слайдера. Я просто отцентровал отрисованные изображения по центру имеющихся размеров. Но это — дело вкуса. Другой вопрос, что preffered-размер будет вычисляться исходя из размера гриппера и пути, просто в данном случае гриппер всяко больше пути.

Итак, таким несложным образом мы получили совершенно по-новому выглядящий слайдер. Теперь для использования его в приложении достаточно задать его как UI для любого JSlider'а:
JSlider mySlider = new JSlider ();
mySlider.setUI ( new GuiSliderUI ( mySlider ) );
И мы получим вот такой вот стилизованный слайдер:

Полный код с примером и графикой можно взять здесь (в нем также реализован вертикальный вариант слайдера, исходные java-классы находятся внутри jar'а).

Понятное дело — каждый отдельный Basic-UI (BasicButtonUI, BasicTextFieldUI, BasicTableUI, BasicTabbedPaneUI и пр.) имеет свои особенности отрисовки частей компонента, впрочем ничего сложного там нет и разобраться не составит труда, тем более что все имеющиеся методы в исходных кодах JDK достаточно подробно прокомментированы и описаны.

Чтобы логически завершить разговор об UI добавлю, что для всех известных J-компонентов помимо Basic-UI есть реализации под различные LaF'ы, к примеру: WindowsSliderUI, MetalSliderUI и прочие. Обычно их называют в соответствии с названием библиотеки или её назначением (для нативных ОС компонентов к примеру — OsnameComponentUI). Иногда их возможно использовать без установки общего LaF, но не всегда — есть такая возможность или нет зависит сугубо от реализации библиотеки (если например при установке LaF'а происходит загрузка стилей, без которых отдельные UI работать просто не будут).

Итак, в данном примере я воспользовался готовой графикой, чтобы создать опрятный визуальный компонент, но можно нарисовать и отличный визуальный компонент пользуясь только стандартными средствами Java, а точнее средствами Graphics2D – об этом пойдет речь далее…

Работа с Graphics2D


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

Отмечу, что во все методы «paint» (а также «paintComponent», «print» и прочие) приходит Graphics, а не Grahics2D, впрочем можно не боясь приводить (cast) его к Grahics2D, если вы работаете с каким-либо J-компонентом или его наследником. Почему же так сложилось? Это, можно так сказать, небольшие остатки старых частей из которых вырос Swing и не думаю, что стоит глубоко в это вдаваться на данный момент. Подробнее данный вопрос освещен в части статьи Skipy об внутреннем устройстве Swing.

Также сразу скажу, что в данной главе приведены лишь несколько вариантов использования графики — на самом деле область её применения гораздо шире. Как раз подробнее о графике и всём, что с ней связано вероятно пойдет речь в моем следующем тематическом топике о Java (при положительном исходе данного и наличии интереса у читателей, конечно). Но хватит спойлеров, вернемся к теме...


Итак, что же мы имеем? Какие возможности дает нам Graphics2D?
Перечислю основные из них:


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

Я уже привел небольшой пример написания своего UI, но есть и другие варианты кастомизации интерфейса. Каждый отдельный J-компонент производит свою Lightweight-отрисовку при помощи метода paint(), который можно легко переопределить и изменить. Напрямую (не всегда, но чаще всего) его лучше не использовать (не буду вдаваться в подробности, так как это целая тема для отдельного топика). Для следующего примера используем метод paintComponent(). Рассмотрим как его можно применить поближе…

Начну с примера — текстовое поле с визуальным фидбэком при отсутствии содержимого:
JTextField field = new JTextField()
{
  private boolean lostFocusOnce = false;
  private boolean incorrect = false;

  {
    // Слушатели для обновления состояния проверки
    addFocusListener ( new FocusAdapter()
    {
      public void focusLost ( FocusEvent e )
      {
        lostFocusOnce = true;
        incorrect = getText ().trim ().equals ( "" );
        repaint ();
      }
    } );
    addCaretListener ( new CaretListener()
    {
      public void caretUpdate ( CaretEvent e )
      {
        if ( lostFocusOnce )
        {
          incorrect = getText ().trim ().equals ( "" );
        }
      }
    } );
  }

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

    // Расширенная отрисовка при некорректности данных
    if ( incorrect )
    {
      Graphics2D g2d = ( Graphics2D ) g;

      // Включаем антиалиасинг для гладкой отрисовки
      g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON );

      // Получаем отступы внутри поля
      Insets insets;
      if ( getBorder () == null )
      {
        insets = new Insets ( 2, 2, 2, 2 );
      }
      else
      {
        insets = getBorder ().getBorderInsets ( this );
      }

      // Создаем фигуру в виде подчеркивания текста
      GeneralPath gp = new GeneralPath ( GeneralPath.WIND_EVEN_ODD );
      gp.moveTo ( insets.left, getHeight () - insets.bottom );
      for ( int i = 0; i < getWidth () - insets.right - insets.left; i += 3 )
      {
        gp.lineTo ( insets.left + i,
            getHeight () - insets.bottom - ( ( i / 3 ) % 2 == 1 ? 2 : 0 ) );
      }

      // Отрисовываем её красным цветом
      g2d.setPaint ( Color.RED );
      g2d.draw ( gp );
    }
  }
};
Наличие содержимого перепроверяется при печати и потере фокуса полем. Переключившись на другой компонент мы увидим как отрисовывается наше дополнение к JTextField'у:

Полный код примера можно взять тут.

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

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

За основу берутся 8 изображений 16х16 — 4 состояния фона чекбокса и 4 состояния галки (5 на самом деле, но 5ое мы добавим програмно):


У стандартного чекбокса, конечно, нету возможности задать спрайты для анимации состояний, к тому же нам нужно наложить изображения галки на фоновые в разных вариациях. Для этого допишем отдельный метод:
public static List<ImageIcon> BG_STATES = new ArrayList<ImageIcon> ();
public static List<ImageIcon> CHECK_STATES = new ArrayList<ImageIcon> ();

static
{
  // Иконки состояния фона
  for ( int i = 1; i <= 4; i++ )
  {
    BG_STATES.add ( new ImageIcon (
        MyCheckBox.class.getResource ( "icons/states/" + i + ".png" ) ) );
  }

  // Дополнительное "пустое" состояние выделения
  CHECK_STATES.add ( new ImageIcon (
      new BufferedImage ( 16, 16, BufferedImage.TYPE_INT_ARGB ) ) );
  
  // Состояния выделения
  for ( int i = 1; i <= 4; i++ )
  {
    CHECK_STATES.add ( new ImageIcon (
        MyCheckBox.class.getResource ( "icons/states/c" + i + ".png" ) ) );
  }
}

private Map<String, ImageIcon> iconsCache = new HashMap<String, ImageIcon> ();

private synchronized void updateIcon ()
{
  // Обновляем иконку чекбокса
  final String key = bgIcon + "," + checkIcon;
  if ( iconsCache.containsKey ( key ) )
  {
    // Необходимая иконка уже была ранее использована
    setIcon ( iconsCache.get ( key ) );
  }
  else
  {
    // Создаем новую иконку совмещающую в себе фон и состояние поверх
    BufferedImage b = new BufferedImage ( BG_STATES.get ( 0 ).getIconWidth (),
        BG_STATES.get ( 0 ).getIconHeight (), BufferedImage.TYPE_INT_ARGB );
    Graphics2D g2d = b.createGraphics ();
    g2d.drawImage ( BG_STATES.get ( bgIcon ).getImage (), 0, 0,
        BG_STATES.get ( bgIcon ).getImageObserver () );
    g2d.drawImage ( CHECK_STATES.get ( checkIcon ).getImage (), 0, 0,
        CHECK_STATES.get ( checkIcon ).getImageObserver () );
    g2d.dispose ();

    ImageIcon icon = new ImageIcon ( b );
    iconsCache.put ( key, icon );
    setIcon ( icon );
  }
}
Остается добавить несколько обработчиков переходов состояний и мы получим анимированный переход между ними:

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

Таким образом путей кастомизации элементов есть достаточно много (даже я, наверно, не знаю/не догадываюсь о некоторых из них). Какой способ выбирать — зависит от ситуации — что необходимо получить, какие изменения возможно сделать в существующем компоненте и т.д.

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

Не буду врать, на данный UI кнопки ушло достаточно много времени, но он создавался без помощи и подсказок дизайнера чистыми средствами Graphics2D и Swing. Здесь можно скачать и изучить полный сурс и демо данного UI, если Вам стало интересно. В нем использован достаточно большой спектр возможностей Graphics2D и применены некоторые хитрости, которые могут зачастую оказаться полезными.

Итак, думаю достаточно разговоров о графике — о ней более подробно я расскажу в будущих топиках, а сейчас приведу немного интересного материала, который я наработал за достаточно долгое время «общения» со Swing и Graphics2D.

DnD и GlassPane


Думаю первое — Вам всем более чем известно, как и проблемы с ним связанные. Насчет второго — вероятно Вы вскользь слышали о GlassPane или может даже видели это старинное изображение (которое до сих пор актуально, между прочем) об устройстве различных слоев стандартных фреймов. Что же тут такого и зачем я вспомнил об этом? И тем более, как связаны DnD и GlassPane спросите Вы? Вот именно о том, как их связать и что из этого может выйти я и хочу рассказать в этой главе.

Чтож, начнем по порядку — что мы знаем о DnD?
У некоторых Swing-компонентов есть готовые реализации для драга (JTree и JList к примеру) — для других можно достаточно легко дописать свою. Чтобы не бросаться словами на ветер — приведу небольшой пример DnD стринга из лэйбла:
JLabel label = new JLabel ( "Небольшой текст для DnD" );
label.setTransferHandler ( new TransferHandler()
{
  public int getSourceActions ( JComponent c )
  {
    return TransferHandler.COPY;
  }
                               
  public boolean canImport ( TransferSupport support )
  {
    return false;
  }

  protected Transferable createTransferable ( JComponent c )
  {
    return new StringSelection ( ( ( JLabel ) c ).getText () );
  }
} );
label.addMouseListener ( new MouseAdapter()
{
  public void mousePressed ( MouseEvent e )
  {
    if ( SwingUtilities.isLeftMouseButton ( e ) )
    {
      JComponent c = ( JComponent ) e.getSource ();
      TransferHandler handler = c.getTransferHandler ();
      handler.exportAsDrag ( c, e, TransferHandler.COPY );
    }
  }
} );
Теперь возможно перетащить текст данного лэйбла прямо из интерфейса в любое другое место, куда возможно вставить текст через дроп.
Фактически — TransferHandler овечает за то, какие данные при драге отдает компонент и как компонент использует входящие данные при драге на него.

Но что делать, если необходимо отследить последовательность действий пользователя при самом перетаскивании?
Для этого есть отдельная возможность повесить слушатель:
DragSourceAdapter dsa = new DragSourceAdapter()
{
  public void dragEnter ( DragSourceDragEvent dsde )
  {
    // При входе драга в область какого-либо компонента
  }    

  public void dragExit ( DragSourceEvent dse )
  {
    // При выходе драга в область какого-либо компонента
  }

  public void dropActionChanged ( DragSourceDragEvent dsde )
  {
    // При смене действия драга
  }

  public void dragOver ( DragSourceDragEvent dsde )
  {
    // При возможности корректного завершения драга
  }

  public void dragMouseMoved ( DragSourceDragEvent dsde )
  {
    // При передвижении драга
  }

  public void dragDropEnd ( DragSourceDropEvent dsde )
  {
    // При завершении или отмене драга
  }
};
DragSource.getDefaultDragSource ().addDragSourceListener ( dsa );
DragSource.getDefaultDragSource ().addDragSourceMotionListener ( dsa );
Остался последний момент — обозначить роль GlassPane. GlassPane, фактически, позволяет располагать/отрисовывать на себе компоненты, как и любой другой контейнер, но его особенность в том, что он лежит поверх всех Swing-компонентов, когда виден. Т.е. если мы что-либо отрисуем на нем, то оно накроет весь находящийся под ним интерфейс. Это позволяет размещать компоненты независимо от основного контейнера в любом месте, создавать любые визуальные эффекты и делать другие занятные вещи.

Приведу для большего понимания небольшой пример подобного «эффекта» — фрейм с несколькими Swing-компонентами на нем. При клике в любой части окна будет появляться эффект «распозающегося» круга, который виден поверх всех элементов. Что самое интересное — подобный эффект не съедает ресурсов и не требует большой груды кода. Не верите? — посмотрите демо и загляните в исходник, вложенный в jar.

Кстати говоря, есть достаточно интересная библиотека на эту тему, заодно предоставляющая дополнительный скролл-функционал и несколько других вкусностей — JXLayer (офф сайт) (описание #1 описание #2 описание #3). К сожалению проекты хостящиеся на сайте java сейчас находтся не в лучшем состоянии, поэтому приходится ссылаться на отдельные ресурсы.

Итак теперь объединим всё что я уже описал в данной главе и сделаем, наконец, что-то полноценное. К примеру — отображение драга панели с компонентами внутри окна:

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

Конечно, для реализации именно данной функциональности не стоило бы прибегать к использованию DnD — есть более короткие пути. Впрочем, всё зависит от ситуации. Данный вариант позволяет отрисовывать перетаскивание независимо от других компонентов в окне. Также можно на основе него, к примеру, реализовать драг панели между окнами приложения.

AWTUtilities


Так как уже достаточно давно в JDK6 включили некоторые будущие нововведения из 7ки, не могу обойти их стороной, так как с их помощью возможно много чего сделать приложив при этом гораздо меньшие усилия.
Итак, нас интересует несколько методов из AWTUtilities:
  1. AWTUtilities.setWindowShape(Window, Shape) — позволяет установить любому окну определенную форму (будь то круг или хитрый полигон). Для корректной установки формы окно не должно быть декорировано нативным стилем (setUndecorated(true)).
  2. AWTUtilities.setWindowOpacity (Window, float) – позволяет задать прозрачность окна от 0 (полностью прозрачно) до 1 (непрозрачно). Окно при этом может быть декорировано нативным стилем.
  3. AWTUtilities.setWindowOpaque (Window, boolean) – позволяет полностью спрятать отображение фона и оформления окна, но при этом любой размещенный на нем компонент будет виден. Для корректной установки данного параметра окно также как и в п.1 не должно быть декорировано нативным стилем.


Что же нам это дает? На самом деле — достаточно широкий спектр возможностей. Окнам своего приложения можно задавать любые хитрые формы какие Вам только потребуются, делать «дырки» посреди приложения, создавать кастомные тени под окно, делать приятные на вид попапы и пр. и пр.

Если переходить к конкретике — setWindowShape на деле я никогда не использую, так как задаваемая окну форма строго обрезается по краю и не очень приятно выглядит. На помощь приходит setWindowOpaque — спрятав оформление и фон окна можно с помощью контейнера с кастомным отрисованным фоном создавать абсолютно любые окна. Приведу небольшой пример использования (в нем также есть также использованы некоторые приемы из предыдущих глав поста):

Здесь можно взять работающий jar с исходным кодом. Честно скажу, что потратил на данный пример не более десяти минут (из них — минут пять прикидывал как расположить элементы внутри диалога :). Естественно это лишь один из вариантов примемения этих новых возможностей — на самом деле их куда больше.

Единственная неприятная мелочь в использовании AWTUtilities – нестабильная работа на Linux-системах. Т.е. Не везде и не всегда корректно отрабатывает прозрачность окон. Не уверен, проблемы ли это текущей JDK или же ОС.

Создание своих интерактивных компонентов


Я уже поверхностно рассказал о том, как создавать компоненты, UI и некоторые «навороты» для интерфейса своего приложения, но что же делать, если нам нужно добавить функциональную часть к компоненту или создать свой совершенно новый компонент с некоей функциональностью и стилизацией? Стилизовать стандартные компоненты и делать из них отдельные части нового компонента достаточно долгое и нудное занятие, тем более что при малейшем изменении в одном из компонентов вся схема может поехать. Именно в таких случаях стоит сделать свой компонент «с нуля».

Итак, за основу лучше лучше всего взять JComponent и используя paint-методы отрисовать его содержимое. Фактически JСomponent сам по себе — чистый холст с некоторыми зашитыми улучшениями для отрисовками и готовыми стандартными методами setEnabled/setFont/setForeground/setBackground и т.п. Как использовать (и использовать ли их) решать Вам. Все, что Вы будете добавлять в методы отрисовки станет частью компонента и будет отображаться при добавлении его в какой-либо контейнер.

Кстати, небольшое отступление, раз уж зашла речь о контейнерах, — любой наследник и сам JComponent являются контейнерами, т.е. могут содержать в себе другие компоненты, которые будет расположены в зависимости от установленного компоненту лэйаута. Что же творится с отрисовкой чайлд-компонентов, лежащих в данном и как она связана с отрисовкой данного компонента? Ранее я не вдавался подробно в то, как устроены и связаны paint-методы Jcomponent'а, теперь же подробно опишу это…

Фактически, paint() метод содержит в себе вызовы 3ех отдельных методов — paintComponent, paintBorder и paintChildren. Конечно же дополнительно он обрабатывает некоторые «особенные» случаи отрисовки как, например печать или перерисовку отдельной области. Эти три метода всегда вызываются в показанной на изображении выше последовательности. Таким образом сперва идет отрисовка самого компонента, затем поверх рисуется бордер и далее вызывается отрисовка чайлд-компонентов, которые в свою очередь также вызывают свой paint() метод и т.д. Естественно есть еще и различные оптимизации, предотвращающие лишние отрисовки, но об этом подробнее я напишу потом.


Компонент отрисован, но статичен и представляет собой лишь изображение. Нам необходимо обработать возможность управления им мышью и различными хоткеями.
Для этого, во-первых, необходимо добавить соответствующие слушатели (MouseListener/MouseMotionListener/KeyListener) к самому компоненту и обрабатывать отдельные действия.

Чтобы не объяснять все на пальцах, приведу пример компонента, позволяющего визуально ресайзить переданный ему ImageIcon:

Здесь можно взять рабочий пример с исходным кодом внутри.

При создании данного компонента я бы выделил несколько важных моментов:
  1. Определяемся с функционалом и внешним видом компонента — в данном случае это область с размещенным на ней изображением, бордером вокруг изображения и 4мя ресайзерами по углам. Каждый из ресайзеров позволяет менять размер изображения. Также есть возможность передвигать изображение по области, «схватив» его за центр.
  2. Определяем все необходимые для работы компонента параметры — в данном случае это само изображение и его «опорные» точки (верхний левый и правый нижний углы). Также есть ряд переменных, которые понадобятся при реализации ресайза и драга изображения.
  3. Накидываем заготовку для компонента (желательно отдельный класс, если Вы собираетесь его использовать более раза) — в данном случае создаем класс ImageResizeComponent, определяем все необходимые для отрисовки параметры, переопределяем метод paintComponent() и отрисовываем содержимое. Также переопределяем метод getPreferredSize(), чтобы компонент сам мог определить свой «желаемый» размер.
  4. Реализовываем функциональную часть компонента — в данном случае нам будет достаточно своего MouseAdapter'а для реализации ресайза и передвижения. При нажатии мыши в области проверяем координаты и сверяем имх с координатами углов и самого изображения — если нажатие произошло в районе некого угла — запоминаем его и при драге изменяем его координату, ежели нажатие пришлось на изображение — запоминаем начальные его координаты и при перетаскивании меняем их. И наконец, последний штрих — в mouseMoved() меняем курсор в зависимости от контрола под мышью.
Ничего сложного, правда? С реализацией «кнопочных» частей других компонентов всё еще проще — достаточно проверять, что нажатие пришлось в область кнопки. Параллельно с отслеживанием событий можно также визуально менять отображение компонента (как сделано в данном примере на ресайзерах). В общем сделать можно всё, на что хватит фантазии.

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

Важно помнить


Есть несколько вещей, которые стоит при любой работе с итерфейсными элементами в Swing – приведу их в этой отдельной небольшой главе.
  1. Любые операции вызова перерисовки/обновления компонентов должны производится внутри Event Dispatch потока для избежания визуальных «подвисаний» интерфейса Вашего приложения. Выполнить любой код в Event Dispatch потоке достаточно легко:
    SwingUtilities.invokeLater ( new Runnable()
    {
      public void run ()
      {
        // Здесь располагаете исполняемый код
      }
    } );
    Важно также помнить, что события, вызываемые из различных listener'ов стандартных компонентов (ActionListener/MouseListener и пр.) исходно вызываются из этого потока и не требуют дополнительной обработки.
  2. Из любых paint-методов ни в коем случае нельзя влиять на интерфейс, так как это может привести к зацикливаниям или дедлокам в приложении.
  3. Представьте ситуацию — из метода paint Вы изменяете состояние кнопки, скажем, через setEnabled(enabled). Вызов этого метода заставляет компонент перерисоваться и заново вызвать метод paint и мы опять попадаем на этот злополучный метод. Отрисовка кнопки зациклиться и интерфейс приложения будет попросту висеть в ожидании завершения отрисовки (или как минимум съест добрую часть процессора). Это самый простой вариант подобной ошибки.
  4. Не стоит производить тяжёлые вычисления внутри Event Dispatch потока. Вы можете произвести эти вычисления в отдельном обычном потоке и затем только вызвать обновление через SwingUtilities.invokeLater().
  5. Не стоит, также, производить тяжёлые вычисления внутри методов отрисовки. По возможности стоит выносить их отдельно и кэшировать/запоминать. Это позволит ускорить отрисовку компонентов, улучшить общую отзывчиввость приложения и снизить нагрузку на Event Dispatch поток.
  6. Для обвноления внешнего вида компонентов используйте метод repaint() (или же repaint(Rectangle) – если Вам известна точная область для обновления), сам метод repaint необходимо исполнять в Event Dispatch потоке. Для обновления же расположения элементов в лэйауте используйте метод revalidate() (его нет необходимости исполнять в Event Dispatch потоке). Метод updateUI() может помочь в некоторых случаях для полного обновления элемента (например смене данных таблицы), но его нужно использовать аккуратно, так как он также отменит установленный Вами UI и возьмет UI предоставляемый текущим LaF'ом.
  7. Полная установка LaF всему приложению отменит любые ваши вручную заданные в ходе работы UI компонентов и установит поверх них те UI, которые предлагает устанавливаемый LaF. Поэтому лучше производить установку LaF при загрузке приложения до открытия каких-либо окон и показа визуальных элементов.
Следовние этим простым пунктам позволит Вам не беспокоиться о возникновении непредвиденных «тормозов» или дедлоков в интерфейсе приложения.
Думаю, что этот список можно дополнить еще несколькими пунктами, но они уже будут весьма специфичны/необязательны.

Итоги


Используя, совмещая и варьируя вышеописанные возможности и особенности работы с интерфейсами возможно добиться любого желаемого результата. Необходимо лишь проявить немного фантазии в этом деле. Надеюсь даже базовые примеры данного поста дали Вам достаточно много пищи для размышлений и, возможно, помогли с чем-либо.

Еще хотелось бы добавить, что возможно, название статьи может показаться Вам слишком громким и не полностью раскрытым — действительно, я все же не дизайнер и не юзабилити-специалист. Я лишь предлагаю Вам инструменты/способы, которые позволят расширить и улучшить интерфейс приложения. Непосредственно бОльшая (не обязательно вся) часть работы по созданию графики для слайдера/кнопки или определению общего стиля компонентов и самого приложения всегда остается за дизайнером — от этого никуда не уйти.

Здесь все примеры статьи в едином «флаконе». Из начального окна можно выбрать желаемый пример:

А также исходники этого последнего «общего» примера.

В завершение, хочу привести несколько источников (часть из них уже упоминалась в статье), которые могут помочь при изучении описанной здесь тематики или же просто могут заинтересовать Вас:
Надеюсь материал статьи оказался Вам хотя бы отчасти полезен.
Если найдутся какие недоработки или ошибки — буду рад дополнить и исправить.

Отдельное спасибо Source Code Highlighter'y за читабельную подсветку кода.

Update1: Выложен новый jar компонента для ресайза изображения с исправлениями
Update2: Выложен новый jar кастомного диалога с исправлениями и плавным появлением/скрытием
Update3: Выложен общий jar со всеми примерами статьи и отдельно все исходники
Автор: @mgarin
ALEE Software
рейтинг 42,41
ПО для электронных архивов и библиотек, оцифровка

Комментарии (71)

  • 0
    Полезный материал. В избранное.
  • +5
    Еще бы все java приложения по умолчанию в gtk использовали gtk'шные же настройки, аля --laf com.sun.java.swing.plaf.gtk.GTKLookAndFeel
    • 0
      Установить его — 2 строчки, так что это не самое страшное все-же :)
      • +2
        само собой, но куда приятнее когда открыл, и сразу получил привычное оформление :)
        • 0
          Ну на Windows, скажем, тоже не WindowsLookAndFeel по умолчанию стоит — если писать кроссплатформенное приложение с нативным стилем — в любом случае придется проставлять стиль для каждой ОС отдельно (по хорошему).
          • +2
            Но согласен, в большинстве случаев нативный стиль был бы более к месту чем дефолтный MetalLookAndFeel.
        • +1
          Помимо оформления если бы еще FileOpenDialog был у Java-приложений не свой, уродский, а родной системный :(
          • +3
            Ну так можно легко использовать NativeSwing и получить нативные красивые диалоги :)
            Я лично теперь так всегда и делаю — сразу гораздо меньше проблем с внешним видом
            • 0
              Эх, если бы вс еразработчики так делали…
              • 0
                Ну, возможно, это не всегда выгодно/возможно. Все же сама библиотека достаточно громоздкая. Впрочем возможно можно выленить из нее непосредственно сами диалоги без остальных интерфейсных вещей.
  • +3
    Я почему-то всегда любил Metal. Вот не знаю почему, но нравится он мне :) За Synthetica спасибо, выглядит наименее чужеродно и наиболее отстранённо от платформ.
    • 0
      Metal был очень хорош на тот момент, когда он только появился. Мы даже решили его оставить в паре крупных проектов. Плюс на всех платформах одинаково смотрится приложение, не нужно ничего подгонять и оптимизировать в интерфейсе.

      Сейчас он смотрится немного устаревшим, как мне кажется. Но это сугубо мое мнение :)
      • +1
        Согласен, немного старовато смотрится, но от этого он не становится хуже. Чисто визуально мне приятно на него смотреть. Но это тоже моё личное мнение и ощущение.
        • 0
          Он хорош своей строгостью и отсутствием лишнего.
          Никакого назойливого моргания элементов, которое отвлекает от дела, и прочих «рюшек» навороченных и нативных стилей.
    • 0
      Поддерживаю. Тоже к Metal прикипел всей душой. Еще Ocean очень люблю. Но вот новый Nimbus… его облик целиком и полностью взят с Mac'ов. Да и ладно, я б даже рад был, но к сожалению копия бездарная! Смотря на него я каждый раз задаюсь вопросом, кто в тогдашней еще Sun занимался дизайном? Программисты?..
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          А какие, собственно, в нем баги?
          Как-то смотрел, но никаких фатальных косяков за ним не заметил — просто по стилю тогда не подошел.
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              Это очень странно, учитывая то что даже в примере, который можно запустить с указанной Вами страницы, есть кнопки с иконками и они вполне себе работают. Плюс на багтрэкере у них по этому поводу тоже ничего. Возможно это просто очень давно было?
              • НЛО прилетело и опубликовало эту надпись здесь
                • 0
                  Вероятно это просто настриваемо? (через LaF или еще как)
                  Просто очень маловероятно, что такой крупный баг не занесли бы в багтрэкер, если уж там другие даже самые мелкие недочеты отметили.
                  Чуть позже покопаю поглубже.
  • 0
    Большое спасибо за исчерпывающий обзор в данной области, ссылки и примеры.
    • 0
      Да не за что — всегда рад осветить полезную информацию :)

      На самом деле, в пост хотелось бы вместить намного больше, но тогда это была бы уже не статья, а целая повесть, так что пришлось максимально ужимать материал. Впрочем, думаю я еще много всего интересного опишу в дальнейших топиках — главное, чтобы было достаточно времени на них.
  • +2
    Очень подробная статья, автор старался. Но за последние 10 лет статей на тему Java UI c уклоном в «засучим рукава и напишем свой paintComponent» накопилось в избытке. Чего катастрофически не хватает и по сей день, так это толковых руководств к систематическому использованию Java UI фреймворков.

    Новички (и не только!) часто находятся перед горой разноцветных виджетов, словно малые дети перед лего конструктором. Им поятно, что вот «это» можно воткнуть в «то» и высветится «гыгы», но дальше-то что?

    Информацию по архитектуре UI призодится вылавливать по крупицам или добывать методом наступания на различные грабли. Что такое UI-Delegate и чем он отличается от MVC?
    Чем отличается MVC от MVP? Где поместить логику приложения? Следят ли UI-Виджеты за изменениями в модели? А наоборот? Какие есть на сегодняшний день толковые Data Binding решения для свинга и есть ли они вообще?

    Если автор действительно постарается
    изложить самые важные и значимые на мой взгляд моменты по работе со Swing и графикой

    то, пожалуй, стоит начать с макромира Java UI, а уж потом углубляться в микроскопические детали.
    • +1
      Спешу не согласиться насчет:
      > за последние 10 лет статей на тему Java UI c уклоном в «засучим рукава и напишем свой paintComponent» накопилось в избытке

      Возможно их и множество, но по 2-3 строчки на каждом ресурсе сложно назвать исчерпывающей информацией — её также приходится искать по крупицам.
      В эту статью я хотел уместить некую поверхностную часть. Далее же можно будет перейти и к макромиру Java UI, к описанию взаимодействия компонентов и их моделей и прочим внутренним вещам. Впрочем эта тематика достаточно объемная и спорная и достаточно сложно будет уместить что-то толковое в один пост.
      • +1
        >Далее же можно будет перейти и к макромиру Java UI, к описанию взаимодействия компонентов и их моделей и прочим внутренним вещам.

        Было бы довольно интересно про это почитать.
        • 0
          Только боюсь это будет не очень скоро, но все же постараюсь не сильно откладывать.
  • +1
    Рекомендую «JGoodies Looks» — качественная настраиваемая шкурка: www.jgoodies.com/downloads/libraries.html

    Пример параметров запуска приложения:
    --cp:p /jgoodies-common-x.x.x.jar:/jgoodies-looks-x.x.x.jar
    --laf com.jgoodies.looks.plastic.Plastic3DLookAndFeel
    -J-DPlastic.defaultTheme=InvertedColorTheme
    • 0
      Как-то я, действительно, упустил JGoodies из виду — добавил в главу по LaF/библиотекам.
  • +1
    Jide имеет commons компоненты под одной из свободных лицензий.
    • 0
      Спасибо, действительно есть такое.
      Дополнил статью.
  • –1
    Думаю, что многих удерживает от изучения или разработки на Java вопросы «как сделать оформление своего приложения ярким и запоминающимся?»

    А я, вот, думаю, что многих удерживает от разработки десктопного софта на Java как раз обратное: «как сделать оформление своего приложения стандартным, не отлтчающимся, в случае Windows, от приложений на C#/WinForms, C++/MFC, WinAPI и т.п.?»
    • 0
      Сам я пишу на Scala (объектно-функционально производной Java, если кто не знает) и лично меня от написания на ней GUI-софта с использованием этой самой Swing удерживает ещё и отсутствие удобной визуальной IDE как VisualStudio для C# и NetBeans для Java (да, NetBeans поддерживает Scala через плагин, но не для дизайна форм). А от написания собственно на Java удерживает тот факт, что после Scala на Java смотришь уже как на QuickBasic после VB.Net или отечественную машину после хорошей иномарки — с ностальгией и удивлением что умудрялся этим пользоваться.
      • +2
        Честно говоря визуальной IDE при разработке интерфейса никогда не пользуюсь (только если в качестве накидать-посмотреть и то потом не использую получившийся код). В ней достаточно долго и муторно подгонять небольшие мелочи и оттачивать интерфейс.
        Впрочем это конечно дело вкуса.

        Насчет сравнения Scala-Java ничего не скажу, так как пока-что со Scala не имел возможности поработать, но думаю она еще появится.
    • 0
      А чем они особо отличаются? Лично я не заметил такого.
    • 0
      > как сделать оформление своего приложения стандартным, не отличающимся, в случае Windows, от приложений на C#/WinForms, C++/MFC, WinAPI и т.п.?

      UIManager.setLookAndFeel ( UIManager.getSystemLookAndFeelClassName () ) — и будет Вам счастье на той же Windows. Или Вы говорите о каких-то отдельных элементах?

      Может конечно где-то очень мелкими частями внешний вид и отличается, но при небольшом видоизменении его не отличишь.
      К примеру, не понятно зачем, в стандартном WindowsComboBoxUI есть бордер, которого у нативных Win-комбобоксов не наблюдается. В сумме — есть несколько таких не особо бросающихся в глаза мелочей.
  • +2
    Отличная статья! Мне особенно понравился настрой автора: на Swing действительно можно написать почти всё, что угодно.

    Хочется привести ссылки на пару библиотек, которые показались мне очень полезными:

    1. Miglayout — это библиотека для позиционирования компонентов на панели. Работать с ней очень просто. При создании layout'а указывается, где что должно находиться (например, "fillx", "5[]5[]5", "10[]10[]10" создаст таблицу с двумя колонками и двумя рядами. Расстояние между колонками будет пять пикселей, между рядами — 10. При изменении размера таблица будет расти по в ширину, но не в высоту.

    2. Timing Framework сильно помогает при создании достаточно сложных анимаций. Увы, анимации делать не тк просто как, скажем в jQuery, но вполне возможно.
    • 0
      Miglayout — интересная штука, ранее не встречал. Чем-то на формы в JGoodies похоже.

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

      P.S. Еще насчет лэйаутов — я по большей части использую 3 лэйаута — BorderLayout, FlowLayout и TableLayout. Иногда еще GridLayout, но очень редко.
      По большей части все же TableLayout — он позволяет сделать практически все что угодно, а главное быстро и легко. Правда кое-где я использовать свою версию, в которой было сделано обновление размеров и получение некоторых параметров.
      • +1
        Обязательно посмотрите этот Miglayout. Он очень напоминает TableLayout. Разработчики добавили туда кучу полезных фич (например, debug). Я теперь только BorderLayout и Miglayout использую.
        • 0
          Ну, как минимум, для некоторых случаев он будет весьма удобен, но не думаю что для всех. Плюс к нему еще привыкнуть надо :)
          • 0
            Кстати, возможно тему о лэйаутах можно также вынести в отдельный топик с пояснениями что и как использовать — их не так уж и много, но у всех есть свои хитрости, а также плюсы и минусы.
  • +1
    Что-то Alloy как-то незаслужено обошли стороной. На мой взгляд это лучший LaF на сегодня. Несмотря на то, что последняя версия вышла в далеком 2004-м году, он работает шустрее и не грузит систему так как Synthetica или Substance.
    Любители IntelliJ IDEA привет!
    • 0
      Согласен — незаслуженно. Добавил в список.
      Сам использую этот стиль в IDEA на самом деле :)
    • +2
      Alloy во-первых, коммерческий, во-вторых уже лет пять как не развивается, в-третьих, не поддерживает сглаживание шрифтов. Тем не менее, наверное лучший LaF, который когда-либо был сделан для Java.
      • 0
        Ну коммерческий или нет — особого значения не имеет. Имеет значение функциональность, поддержка продукта и его развитие.
        Даже учитывая, что он уже не развивается, он вполне себе живет и процветает во многих современных приложениях.
      • 0
        Да сглаживание шрифтов это ведь решаемо. Переопределить метод paintComponent > выставить там Rendering Hints… и будут в Alloy гладкие шрифты. Ну в общем вы, уверен, знаете… Вот то, что платный он — вот это самая досадная штука. Досадная потому, что покупать этот LaF сейчас не имеет смысла — проект стоит уже более пяти лет, а по сети гуляет ключ к нему, забесплатно — бери и юзай… Да и дороговато просят… Использовать же Alloy на широкую ногу в своих проектах, с тем же ломаным ключом, вроде как тоже нельзя — Alloy по-прежнему остается коммерческим. Вот и не знаешь что делать… Досадно ужасно, красивый и качественный LaF.
        • 0
          Мы его где-то лет пять назад и купили, после того, как увидели интерфейс JProfiler-а. И пользуем до сих пор. А со сглаживанием все сложнее. Переопределять paintComponent для всех компонентов свинга — дело накладное. Есть пара методов, например установить свой RepaintManager. Но с untrusted апплетами не работает.
          • 0
            > Переопределять paintComponent для всех компонентов свинга — дело накладное.
            На самом деле, в большинстве случаев все-равно с каждым элементом идет куча стандартных настроек — в итоге и без необходимости переопределения paintComponent рано или поздно накапливается «дублирующий» Swing'овые компоненты набор. Да и компонентов там на амом деле не так много — не более 15-20 :)
  • 0
    Немного поздно, но все же…
    Обновил примеры статьи — выложил дополнительно все примеры и все исходные коды сгрупированно.
    • 0
      Не могли бы вы выложить на Народ.Диск?
      А то из-за NAT с rapidshare не особо покачаешь.
      • +1
        Перезалил на наш собственный сайт — вчера не успел сделать т.к. ночью хабр лежал :(
        Тут можно взять примеры, а тут все исходники.
        Также обновил все ссылки в статье.
        • 0
          Благодарю!
  • 0
    А как сделать чтобы на каждой системе окна выглядели в текущей цветовой схеме этой системы. Коробит, когда одно окно выделяется.
    • 0
      Сами окна или же их компоненты?
      И на какой конкретно системе?

      Скажем под тем же Windows нет цветовых схем самих компонентов (если не учитывать несерьезные стили), а цвет оформления окна Java-приложения будет таким же как и у других приложений.
  • 0
    Что-то я не могу понять, а что мешает использовать SWT?
    Так как он нативен, то отпадают все проблемы, с отображением контролов в стиле системы.
    • 0
      И при этом отпадает вариант работы со Swing. Всё гладко не бывает — везде есть минусы.
      Впрочем если в приложении требуются только стандартные компоненты в точном нативном оформлении то SWT действительно наилучший вариант.
  • 0
    Извините за возможно глупый вопрос, а JavaFx можно прикрутить к этому делу, ну например, анимированная кнопка написанная на JavaFx?
    • 0
      Думаю вам может подсказать, скажем, эта статья.
      А вообще подобного материала достаточно много разного везде :)
      • 0
        Вот еще один вариант
  • 0
    Великолепная статья!
    Очень бы мне пригодилась во времена, когда я писал десктоп приложения на Java.
    Кстати вы забыли упомянуть о такой замечательной библиотеке JDIC, которая позволяет в Swing просматривать интернет страницы, вордовские документы и прочее няшки.

    И кстати, интересно, как бы вы сделали программно на Swing окно, которое бы имело скругленные углы и полупрозрачные края? Причем в местах скругления не должно быть «зазубрин», т.е. необходим эдакий антиалайзинг. Я решение так и не получил полностью красивого…
    • +1
      Насчет JDIC я писал в предыдущей статье и решил повторно не упоминать :)
      К тому же по тематике к данной статье не совсем подходило.

      Насчет корректно алиасенного кастомного окна — через установку shape'а окна это не пройдет — форма будет без алиаса. Тут есть хитрость (о которой я вроде бы упоминал в статье?) — необходимо установить окну setUndecorated(екгу) и AWTUtilities.setWindowOpaque(window, false) и поверх него рисовать форму.
      Вот небольшой пример реализации:
      public class CustomShapeWindow extends JFrame
      {
        public CustomShapeWindow () throws HeadlessException
        {
          super ( "Кастом фрейм" );

          getContentPane ().add ( new JComponent()
          {
            {
              setOpaque ( false );
            }

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

              Graphics2D g2d = ( Graphics2D ) g;
              g2d.setRenderingHint ( RenderingHints.KEY_ANTIALIASING,
                  RenderingHints.VALUE_ANTIALIAS_ON );

              g2d.setPaint ( Color.LIGHT_GRAY );
              g2d.fillRoundRect ( 0, 0, getWidth (), getHeight (), 100, 100 );
            }
          } );

          setUndecorated ( true );
          AWTUtilities.setWindowOpaque ( this, false );
          setSize ( 400, 400 );
          setLocationRelativeTo ( null );
          setVisible ( true );
        }

        public static void main ( String[] args )
        {
          new CustomShapeWindow ();
        }
      }
      Это конечно самый базовый вариант. Можно использовать метод paint() окна, а не компонента в нем, и далее располагать поверх компоненты как Вам удобно. Можно отрисовывать на панели и поверх нее громоздить UI. Вариантов уйма.

      P.S. Извиняюсь за поздний ответ — пропустил уведомление мимо глаз :(
      • 0
        Спасибо за столь обширный ответ!
        • 0
          Надеюсь это то, что Вы искали :)
          • 0
            Именно :-)
            • 0
              Кстати диалог в примерах данной статьи (с полем ввода) сделан именно таким способом.
  • 0
    Отличная статья. Нашел кое-что новое для себя =)
    • 0
      Спасибо! Возможно и в других статьях цикла Вы сможете найти немало полезного.
  • 0
    Для приложения нужен нормальный (удобный, компактный) color chooser. Из-за одного компонента неохота тащить лишних 3Мб, особенно учитывая то, что само приложение весит меньше мегабайта. Есть ли способ выбить из библиотеки только WebColorChooserField и необходимые ему классы?
    • 0
      С последней версией это будет сделать сложнее т.к. сам color chooser теперь использует UI вместо отдельного компонента. Можно попробовать расковырять WebColorChooserUI и вынуть из него панель на которой все компоненты закреплены. В целом — она должна вполне нормально работать и отдельно от WebLaF, если вынести всё лишнее.
      • 0
        Мда, пожалуй, проще свой chooser написать.
        • 0
          Можете просто взять за основу некоторые сложные части, а в остальном да — проще написать свою альтернативу, если конечно размер приложения настолько критичен для вас. В целом сейчас плюс-минус пара метров в размере десктопного приложения ничего не решат (в разумных границах, естественно), если только вы не собираетесь показывать этот color chooser в апплете.

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

Самое читаемое Разработка