65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Java 应用程序中托管 WPF 内容

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (8投票s)

2009年4月26日

CPOL

4分钟阅读

viewsIcon

74979

downloadIcon

3119

演示将 WPF/.NET 组件嵌入 Java GUI 的简单技术

引言

这是又一篇关于 Java/.NET 集成的文章。

背景

Java/.NET 集成的主要思想在 [4] 中进行了描述。我在其中提供了将 .NET GUI 组件嵌入 Java GUI 的代码示例。如果读者不熟悉面向对象的 .NET JNI,我建议先阅读这篇文章。对于这个演示项目,我使用了与 [1] 相同的 WPF 组件。

集成 Java 应用程序与 WPF/.Net 组件模块

Java 和 .NET 代码集成的基本思想在 [4] 中进行了描述,其中我展示了一个将 .NET GUI 组件嵌入 Java 界面的示例。对于任何不熟悉面向对象的 .NET JNI 的人,我建议先阅读这篇文章。Windows Presentation Foundation (WPF) 的描述可以在 MSDN 和专门的手册中找到,本文不作讨论。我示例中使用的 WPF 模块(无源代码)是从 [1] 借用的。在这里,我只设置了将 WMF 组件嵌入 Java GUI 的任务。

编写 Java 代码

从 Java 端来看,任何 WPF/.NET 组件都是一段本机代码,需要用一些 Java 类进行包装。Java 中有一个特殊的接口用于处理 WPF/.NET 组件。接口必须包含用于

  • 创建组件实例(create
  • 当 Java 代码退出时销毁实例(dispose

在附带的示例代码中,我将 WPF Clock 组件 [1] 嵌入 Java GUI。updateClock 方法设置新的日期/时间。任何本机 GUI 元素都可以嵌入 Java 用户界面组件中,使用 java.awt.Canvas 类作为基 Java UI 组件类。java.awt.Canvas 是一个具有窗口的 Java 组件,即一个重量级组件,它不实现 paint 方法。com.oojni.WPFContainer 类继承自 java.awt.Canvas。任何 WPF 组件都是无窗口的,必须添加到 .NET 容器中,该容器绑定到 java.awt.Canvas 类的窗口。
在提供的 Java 代码示例中,占位符用作 .NET 组件的互操作。

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

/**
 * The interop for WPF Container
 * 
 * @author Vitaly Shelest
 */
public class WPFContainer extends Canvas {
    static{
        System.loadLibrary("oojni.net20"); 
        System.loadLibrary("JavaHostWPF");
    }
    /**
     * Stores a reference to .NET Container
     */
    int ref = 0;
    /**
     * The place to create .NET Container
     */
    public void addNotify() {
        super.addNotify();
        ref = create(this);
    }
    /**
     * Used to dispose .NET Container
     */
    public void removeNotify() {
        dispose(ref);
        super.removeNotify();
    }
    /**
     * Disposes .NET Container
     */
    public void dispose()
    {
        if(ref != 0)
            dispose(ref);
        ref = 0;
    }
    /**
     * Updates WPF Clock with a new time/date selection
     */
    void updateClock()
    {
        updateClock(ref);
    }
    /**
     * Updates WPF Clock
     * @param ref reference to .NET Container
     */
    native void updateClock(int ref);
    /**
     * Creates .NET Container
     *   @param parent Java component as a placeholder for .NET Container
     * @return reference to .NET Container
     */
    native int create(Object parent);
    /**
     * Disposes .NET Container
     * @param ref reference to .NET Container
     */
    native void dispose(int ref);
}

该类重写了两个 java.awt.Canvas 方法

  • addNotify,在 JVM 创建 java.awt.Canvas 实例时调用。这是创建本机 WPF/.NET 组件容器的地方,本机 create 方法返回一个整数值(我称之为 .NET 组件引用)。在 .NET 代码中,当执行本机方法时,此值将被转换为 WPF/.NET 组件容器实例。
  • removeNotify,在 JVM 销毁 java.awt.Canvas 实例时调用。在此方法中,使用本机方法 dispose 销毁 WPF/.NET 组件容器。
  • Calendar Container - com.oojni.CalendarContainer 也设计了相同的互操作。其余代码实现了 Java 应用程序的常规SWING GUI

.NET JNI 模块设计

在 .NET JNI 代码中,我实现了以下方法:

  • 从 Java 组件获取窗口句柄 hWnd。句柄 hWnd 将进一步利用
  • create 方法的本机实现中创建 .NET 组件容器实例,并将此 .NET 组件容器的 hWnd 添加为 Java 组件的子窗口
  • 用 .NET GUI 组件填充 .NET 组件容器实例

为了开发本机模块JavaHostWPF.dll,我使用了 [3]。

该图演示了 Java 和 .NET/WPF 组件之间的关系。在 .NET 模块中,Java WPFContainer 类的本机方法实现为常规 .NET 方法。创建的方法将 Java WPF 容器的 HWND 绑定到 .NET WPF Container 类实例。该类可以接受 .NET 组件。WPF 组件在 .NET WPF Container 中的托管实现方式如 MSDN 所述:.NET WPF Container -> HwndSource -> WpfComponent。唯一的问题是 WPF 组件必须在 STA 线程上下文中运行。为了满足此要求,.NET WPF 容器实例运行在单独的 STA 中。Java 代码调用在此线程的上下文中执行。

这是 com.oojni.WPFContainer 本机方法 create 的实现。

/// <summary>
/// Implements Native Method create that creates Component Container reference
/// </summary>
/// <param name="parent">com.oojni.CalendarContainer instance</param>
/// <returns>WPF Clock Container reference</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 线程中,我调用 ObjectOrientedJNIInterop.com.oojni.WPFCreator 类实例的 create 方法,其中创建了 .NET WPF 容器(类 ObjectOrientedJNIInterop.com.oojni.WPFControl)。在 ObjectOrientedJNIInterop.com.oojni.WPFControlOnLoad 事件中,我创建了 WPF Clock 组件。

private void WPFControl_Load(object sender, EventArgs e)
{
    // Create WPF Clock Component instance here.
    // First wrap HWND of Java Placeholder.
    ObjectOrientedJNIInterop.java.awt.CanvasJNIProxy canvas = 
	new ObjectOrientedJNIInterop.java.awt.CanvasJNIProxy(parent);
    // Initialize create parameters
    System.Windows.Interop.HwndSourceParameters sourceParams = 
	new System.Windows.Interop.HwndSourceParameters("JavaWPFApp");
    // Set Size and Location of WPF Clock
    sourceParams.PositionX = 0;
    sourceParams.PositionY = 0;
    sourceParams.Height = canvas.getHeight();
    sourceParams.Width = canvas.getWidth();
    sourceParams.ParentWindow = this.Handle;
    sourceParams.WindowStyle = WS_VISIBLE | WS_CHILD;
    // Wrap EmbeddedFrame HWND, this component is embedded into Java Placeholder
    hwndSource = new System.Windows.Interop.HwndSource(sourceParams);
     // Set Date/Time to WPF Clock and put WPF Clock component into 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 代码展示了如何在 STA 线程上下文中调用 Java 本机方法。

delegate void UpdateClock(int peer);
/// <summary>
/// Implementation of native method updateClock
/// </summary>
/// <param name="peer">WPF Clock Container reference</param>
public override void updateClock(int peer)
{
    // Restore the instance of WPF Clock Container
    GlobalReference gr = new GlobalReference(peer, true);
    object o = gr.Object;
    // Call updateClock in STA Thread Context
    if (((ObjectOrientedJNIInterop.com.oojni.WPFControl)o).InvokeRequired)
        ((ObjectOrientedJNIInterop.com.oojni.WPFControl)o).Invoke
	(new UpdateClock(updateClock), new object[] { peer });
    else
    {
        // Initialize WPF Clock Component with new values
        ((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

参考文献

  1. Choong,K.,2008, 在 MFC 应用程序中托管 WPF 内容 [在线],CodeGuru
  2. Shelest,V.,2007, OOJNI Add-in .NET (C#) for VS2005/2008 [在线],Simtel
  3. Shelest,V.,2006, OOJNI for .NET2.0 (low-level) [在线],Simtel
  4. Shelest,V.,2006, Java/.NET 集成尽可能的简单 [在线],The Code Project

谢谢

非常感谢 Ilya Shpilberg 在撰写本文时给予的宝贵建议。

历史

  • 2009年4月26日:初始发布
© . All rights reserved.