Внедрение содержимого WPF в приложение Java

·         Скачать демонстрационный проект - 130.98 Кб

·         Скачать исходный код - 37.93 Кб


Введение

Это очередная статья об объединении Java/.NET.

Вводные сведения

Здесь дается пример кода для внедрения компонентов .NET GUI в Java GUI. Если читатель не знаком с объектно-ориентированным JNI (интерфейс для прямого доступа из Java) для .NET, желательно сначала прочитать эту статью.

Описание основы презентаций Windows (WPF) можно найти в MSDN (сеть для разработчиков Microsoft), специальных руководствах и  в данной статье не обсуждается. Здесь излагается только задача внедрения компонента WMF в Java GUI.

Написание кода Java

С точки зрения Java любой компонент WPF/.NET – кусок внутреннего кода, который должен быть обернут некоторыми классами Java. Специальный интерфейс в Java имеет дело с компонентами WPF/.NET. Интерфейс должен включать методы для:
•    создания экземпляров компонента (создать)
•    уничтожения экземпляров, когда код Java завершает работу (удалить)

В приложенном примере кода компонент часов WPF [1] внедрен в Java GUI. Метод updateClock устанавливает новую дату/время. Произвольный внутренний элемент GUI может быть внедрен в компоненты пользовательского интерфейса Java с классом java.awt.Canvas в качестве базового класса компонента пользовательского интерфейса Java.

java.awt.Canvas - компонент Java с окнами, т.е. тяжелый компонент, не реализующий метод paint. Класс com.oojni.WPFContainer наследуется от java.awt.Canvas. Любой компонент WPF не имеет окон и должен быть добавлен в контейнер .NET, связанный с окном класса java.awt.Canvas.

В предоставленном примере кода Java есть метки-заполнители, используемые в качестве функциональной совместимости interop для компонентов .NET. Функциональная совместимость interop для компонента WPF - класс com.oojni.WPFContainer:

package com.oojni;
import java.awt.Canvas;

/**
 * Интероп для контейнера WPF
 *
 * @автор Vitaly Shelest
 */
public class WPFContainer extends Canvas {
    static{
        System.loadLibrary("oojni.net20");
        System.loadLibrary("JavaHostWPF");
    }
    /**
     * хранит ссылку на контейнер .NET
     */
    int ref = 0;
    /**
     * здесь создается контейнер .NET
     */
    public void addNotify() {
        super.addNotify();
        ref = create(this);
    }
    /**
     * используется для удаления контейнера .NET
     */
    public void removeNotify() {
        dispose(ref);
        super.removeNotify();
    }
    /**
     * удаляет контейнер .NET
     */
    public void dispose()
    {
        if(ref != 0)
            dispose(ref);
        ref = 0;
    }
    /**
     * обновляет часы WPF с помощью новой выборки время/дата
     */
    void updateClock()
    {
        updateClock(ref);
    }
    /**
     * обновляет часы WPF
     * @параметр ref ссылается на контейнер .NET
     */
    native void updateClock(int ref);
    /**
     * создает контейнер .NET
     *   @параметр родительский компонент Java как метка-заполнитель для контейнера .NET
     * @возврат ссылки на контейнер .NET
     */
    native int create(Object parent);
    /**
     * удаляет контейнер .NET
     * @параметр ref ссылка на контейнер .NET
     */
    native void dispose(int ref);
}

Данный класс переопределяет два метода java.awt.Canvas:

•    addNotify, вызываемый JVM (виртуальная машина Java) при создании экземпляра java.awt.Canvas. Это место, в котором может быть создан контейнер внутреннего компонента WPF/.NET, внутренний метод create (создать) вернет некоторое целое значение (называемое ссылкой на компонент .NET). В коде .NET это значение преобразуется в экземпляр контейнера компонента WPF/.NET, когда выполняется внутренний метод.
•    removeNotify, вызываемый JVM при уничтожении экземпляра java.awt.Canvas. В данном методе контейнер компонента WPF/.NET уничтожается с помощью внутреннего метода dispose.
•    Такой же интероп был разработан для контейнера календаря - com.oojni.CalendarContainer. Остальная часть кода реализует обычный SWING GUI приложения Java.

Конструкция модуля .NET JNI

В коде .NET JNI реализованы методы, которые:
•    Извлекает дескриптор окна hWnd из компонента Java. Дескриптор hWnd используется позднее
•    Создает экземпляр контейнера компонента .NET во внутренней реализации метода create и добавляет hWnd данного контейнера компонента .NET в качестве дочернего окна компонента Java
•    Заполняет экземпляр контейнера компонента .NET компонентами .NET GUI

Для разработки внутреннего модуля JavaHostWPF.dll был использован.


     Данная схема показывает связь между компонентами Java и .NET/WPF. В модуле .NET внутренние методы класса Java WPFContainer реализованы как стандартные методы .NET. Созданный метод связывает HWND контейнера Java WPF с экземпляром класса .NET WPF Container. Этот класс может принимать компоненты .NET. Размещение компонента WPF в .NET WPF Container реализовано в соответствии с описанным в MSDN: .NET WPF Container -> HwndSource -> WpfComponent. Единственная проблема в том, что компоненты WPF должны выполняться в контексте потока STA. Чтобы выполнить данное требование, экземпляр контейнера .NET WPF выполняется в отдельном STA. Вызовы кода Java выполняются в контексте данного потока.

Ниже приводится реализация внутреннего метода create com.oojni.WPFContainer:

/// <summary(аннотация)>
/// Реализует внутренний метод create, создающий ссылку на контейнер компонента
/// </summary>
/// <param name="parent">экземпляр com.oojni.CalendarContainer</param>
/// <returns>ссылка на контейнер часов WPF</returns>
public override int create(ObjectOrientedJNI.JObject parent) {
    WPFCreator creator = new WPFCreator(parent);
    thread = new System.Threading.Thread
        (new System.Threading.ThreadStart(creator.Create));
    thread.SetApartmentState(System.Threading.ApartmentState.STA);
    thread.Start();
    creator.autoEvent.WaitOne();
    GlobalReference gref = new GlobalReference(creator.Control, true);
    return gref.Reference.ToInt32();
}

В потоке STA вызывается метод create экземпляра класса ObjectOrientedJNIInterop.com.oojni.WPFCreator, в котором создается контейнер .NET WPF (класс ObjectOrientedJNIInterop.com.oojni.WPFControl). В событии OnLoad (после загрузки) ObjectOrientedJNIInterop.com.oojni.WPFControl создается компонент часов WPF:

private void WPFControl_Load(object sender, EventArgs e)
{
    // здесь создается экземпляр компонента часов WPF.
    // сначала обертывается HWND метки-заполнителя Java.
    ObjectOrientedJNIInterop.java.awt.CanvasJNIProxy canvas =
    new ObjectOrientedJNIInterop.java.awt.CanvasJNIProxy(parent);
    // инициализируются параметры create    System.Windows.Interop.HwndSourceParameters sourceParams =
    new System.Windows.Interop.HwndSourceParameters("JavaWPFApp");
    // задаются размер и местоположение часов WPF
    sourceParams.PositionX = 0;
    sourceParams.PositionY = 0;
    sourceParams.Height = canvas.getHeight();
    sourceParams.Width = canvas.getWidth();
    sourceParams.ParentWindow = this.Handle;
    sourceParams.WindowStyle = WS_VISIBLE | WS_CHILD;
    // обертывается EmbeddedFrame HWND, этот компонент внедряется в метку-заполнитель Java   
hwndSource = new System.Windows.Interop.HwndSource(sourceParams);
     // присваивается дата/время часам WPF, и компонент часы WPF вставляется в HwndSource
    DateTime tm = DateTime.Now;
    clock = new WPFControls.AnimClock();
    clock.ChangeDateTime(tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
    System.Windows.FrameworkElement myPage = clock;
    hwndSource.RootVisual = myPage;
}

Данный кусок кода .NET показывает, как вызывать внутренний метод Java в контекст потока STA. 

delegate void UpdateClock(int peer);
/// <summary>
/// реализация внутреннего метода updateClock
/// </summary>
/// <param name="peer">ссылка на контейнер часов WPF</param>
public override void updateClock(int peer)
{
    // восстанавливается экземпляр контейнера часов WPF
    GlobalReference gr = new GlobalReference(peer, true);
    object o = gr.Object;
    // вызывается updateClock в контексте потока STA
    if (((ObjectOrientedJNIInterop.com.oojni.WPFControl)o).InvokeRequired)
        ((ObjectOrientedJNIInterop.com.oojni.WPFControl)o).Invoke
    (new UpdateClock(updateClock), new object[] { peer });
    else
    {
        // инициализируется новыми значениями компонент часов WPF        ((ObjectOrientedJNIInterop.com.oojni.WPFControl)o).clock.ChangeDateTime
    (CalendarCreator.currentDateTime.Year, CalendarCreator.currentDateTime.Month,
    CalendarCreator.currentDateTime.Day, CalendarCreator.currentDateTime.Hour,
    CalendarCreator.currentDateTime.Minute,
    CalendarCreator.currentDateTime.Second);
        System.Windows.FrameworkElement myPage =
        ((ObjectOrientedJNIInterop.com.oojni.WPFControl)o).clock;
                ((ObjectOrientedJNIInterop.com.oojni.WPFControl)o).
                    hwndSource.RootVisual = myPage;
    }
}

Необходимые условия для запуска приложенного примера

Средства разработки
•    Microsoft Visual Studio 2005 или выше
•    Microsoft .NET Framework 3.0 или выше
•    Java SUN/IBM 1.6 и выше
•    Java SUN/IBM 1.3 и выше для перекомпиляции исходников Java

Операционные системы
•    Windows Vista
•    Windows XP SP2
•    Windows Server 2003 SP1