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






4.94/5 (8投票s)
演示将 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.WPFControl
的 OnLoad
事件中,我创建了 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
参考文献
- Choong,K.,2008, 在 MFC 应用程序中托管 WPF 内容 [在线],CodeGuru
- Shelest,V.,2007, OOJNI Add-in .NET (C#) for VS2005/2008 [在线],Simtel
- Shelest,V.,2006, OOJNI for .NET2.0 (low-level) [在线],Simtel
- Shelest,V.,2006, Java/.NET 集成尽可能的简单 [在线],The Code Project
谢谢
非常感谢 Ilya Shpilberg 在撰写本文时给予的宝贵建议。
历史
- 2009年4月26日:初始发布