免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1746 | 回复: 0
打印 上一主题 下一主题

做你自己的 SWT控件 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2005-03-26 15:14 |只看该作者 |倒序浏览
When writing applications, you typically use the standard widgets provided by SWT. On occasion, you will need to create your own custom widgets. For example, you might want to add a new type of widget not provided by the standard widgets, or extend the functionality of an existing widget.  This article explains the different SWT extension strategies and shows you how to use them.
Copyright © 2001 Object Technology International, Inc.
Eclipse Corner Article
  
Creating Your Own Widgets using SWT

Summary
When writing applications, you typically use the standard widgets provided by SWT. On occasion, you will need to create your own custom widgets. For example, you might want to add a new type of widget not provided by the standard widgets, or extend the functionality of an existing widget.  This article explains the different SWT extension strategies and shows you how to use them.

By Steve Northover & Carolyn MacLeod, OTI
March 22, 2001

--------------------------------------------------------------------------------
Creating Your Own Widgets
Overview
When writing applications, you typically use the standard widgets provided by SWT. On occasion, you will need to create your own custom widgets. There are several reasons that you might want to do this:
·        To add a new type of widget not provided by the standard widgets
·        To extend the functionality of an existing widget

Custom widgets are created by subclassing in the existing widget class hierarchy.
Portability Issues
It is very important to think about portability before writing a custom widget. SWT can be extended in the following ways:
·        Write a new widget that is 100% Java™ portable
·        Extend an existing widget in a 100% Java portable manner
·        Write a new widget that wraps an existing native widget – not portable
·        Extend an existing widget by calling natives – not portable

In addition, a combination of these can be used on different platforms:
·        Write a new widget that wraps an existing native widget on one platform, but is 100% Java portable on other platforms
·        Extend an existing widget by calling natives on one platform, but call 100% Java portable code on other platforms
This of course involves implementing the widget twice – using native calls on the one platform and portable code on the others – while maintaining the same API for both.

Each SWT platform is shipped with both a shared library (for example, a DLL on Windows®) and a jar (for the Java class files). The shared library contains all of the native function required for SWT, but it was not meant to be a complete set of the functions available on the platform. Thus to expose native function or native widgets that were not exposed by SWT, you need to write your own shared library. If you are using a combination of native code on one platform and portable code on another, make sure you call your shared library on the platform with the native widget, and your jar on the platform with the portable widget.

One final note: SWT’s interface to its shared libraries is internal SWT code. It was not meant to provide a framework for applications to access all possible native function on all platforms – that would be a daunting task. One of the purposes of this document is to show how you can integrate C code with SWT, not model the operating system. As such, the approach taken to writing natives in this document is different from the approach taken by SWT.
Writing Portable Widgets
The SWT library provides two widget classes that are typically used as the basis for a custom 100% Java portable widget:
·        Canvas - to create basic widgets
·        Composite - to create compound widgets
Basic Widgets
Basic widgets do not contain any other widgets, and are not built from any other widgets. Basic widgets draw themselves. An example of a basic widget is Button. Another example is Text. To create a custom basic widget, subclass Canvas.
Compound Widgets
Compound widgets contain other widgets, and/or are composed of other widgets. An example of a compound widget is Combo. It contains a Text, a Button and a List. Another example is Group. It can contain any number of children. To create a custom compound widget, subclass Composite.

The astute reader may have noticed that Canvas is actually a subclass of Composite. This is an artifact of the underlying implementation. We treat Canvas as something you draw on and Composite as something that has children. Therefore the rule for deciding which class to subclass is this: If your widget has or will have children, subclass Composite. If your widget does not have and never will have children, subclass Canvas.

Note also that we do not distinguish between a compound widget that is intended to contain and lay out children, and one that is merely composed of other widgets. Both will be a subclass of Composite, and as such we are describing implementation, rather than type, inheritance. When writing 100% Java portable widgets, we can think of Composite as the portable entry point into the SWT class hierarchy for all compound widgets, and Canvas as the portable entry point into the SWT class hierarchy for all basic widgets, regardless of widget type.

Basic Widget Example
Imagine we are building an application where we need a widget that displays an image with a line of text to the right, something like this:

Since we plan to draw both the image and the text, we subclass Canvas.

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;

public class PictureLabel extends Canvas {
  Image image;  
  String text;
}

Our widget needs to be created. To do this, we must write at least one constructor. Because widgets in SWT cannot be created without a parent, the constructor must take at least one argument that is the parent. The convention in SWT is to have a constructor with two arguments, parent and style. Style bits are used to control the look of widgets. Neither the parent nor the style bits can be changed after the widget is created. Your widget can use style bits too.

  PictureLabel(Composite parent, int style) {
     super(parent, style);
  }  

The parent of any widget must be a Composite. The style is an integer, where some bits are already used by the system. For example, SWT.BORDER will cause a Canvas to have a border.

Next we need to initialize our widget. The convention in SWT is to do all initialization in the constructor. Certainly, any initialization that requires the parent or the style bits must be done here. We have decided that our PictureLabel widget will default to a white background, so we need to add a Color field, allocate a Color, and initialize the background.

public class PictureLabel extends Canvas {
  Image image;
  String text;
  Color white;
  
  PictureLabel(Composite parent, int style) {
     super(parent, style);
     white = new Color(null, 255, 255, 255);
     setBackground(white);

Colors are graphics resources that must be disposed. How can we dispose of the white color that we allocated? We add a dispose listener. Every widget provides notification when it is destroyed. We add the dispose listener in the constructor.

     addDisposeListener(new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
                white.dispose();
         }
     });
  }
}

Note: Do not just override dispose() to release the color. This only works in the case where dispose is actually sent to the widget. When the shell is disposed this does not happen, so overriding dispose will leak the color. To ensure that your widget is informed of an event no matter how it was generated, add an event listener instead of overriding methods that generate events.

Our widget is created and initialized, and it can be destroyed without leaking graphics resources. Now it needs some functionality. We need to draw the image and the text, and this will require another listener: the paint listener. Implementing a widget often requires adding many listeners. We could implement the listener interfaces as part of our new widget class, but that would make the interface methods public in our class. Instead, the SWT convention is to use anonymous inner classes to forward the functionality to non-public methods of the same name. For consistency, we will rewrite the dispose listener to follow this convention, moving the color dispose code into the widgetDisposed method. We write the paint listener the same way.

     addDisposeListener(new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
            PictureLabel.this.widgetDisposed(e);
         }
     });
     addPaintListener(new PaintListener() {
         public void paintControl(PaintEvent e) {
            PictureLabel.this.paintControl(e);
         }
     });

By choosing the same names, we have the option of easily implementing the interfaces if we decide to do so later. Here is the paintControl method to draw the widget.

  void paintControl(PaintEvent e) {
     GC gc = e.gc;
     int x = 1;
     if (image != null) {
         gc.drawImage(image, x, 1);
         x = image.getBounds().width + 5;
     }
     if (text != null) {
         gc.drawString(text, x, 1);
     }  
  }

Now we can draw the image and the text, but we need to let the user set them. So we write set and get methods for each of them.

  public Image getImage() {
     return image;
  }

  public void setImage(Image image) {
     this.image = image;
     redraw();
  }

  public String getText() {
     return text;
  }

  public void setText(String text) {
     this.text = text;
     redraw();
  }

The get methods are trivial. They simply answer the fields. The set methods set the fields and then redraw the widget to show the change. The easiest way to do this is to damage the widget by calling redraw(), which queues a paint event for the widget. This approach has the advantage that setting both the image and the text will cause only one paint event because multiple paints are collapsed in the event queue.

We are not done yet. Our widget does not know its preferred size. This information is needed in order to lay out the widget. In our case, the best size is simply the size of the text plus the size of the image, plus a little bit of space in between. Also, we will add a 1 pixel margin all the way around.

To return the preferred size of the widget, we must implement the computeSize method. The computeSize method can be quite complicated. Its job is to calculate the preferred size of the widget based on the current contents. The simplest implementation ignores the arguments and just computes the size.

  public Point computeSize(int wHint, int hHint, boolean changed) {
     int width = 0, height = 0;
     if (image != null) {
         Rectangle bounds = image.getBounds();
         width = bounds.width + 5;
         height = bounds.height;
     }
     if (text != null) {
         GC gc = new GC(this);
         Point extent = gc.stringExtent(text);
         gc.dispose();
         width += extent.x;
         height = Math.max(height, extent.y);
     }
     return new Point(width + 2, height + 2);     
  }

What are wHint, hHint, and changed? The hint arguments allow you to ask a widget questions such as “Given a particular width, how high does the widget need to be to show all of the contents”? For example, a word-wrapping Label widget might be asked this. To indicate that the client does not care about a particular hint, the special value SWT.DEFAULT is used. The following example asks a label for its preferred size given a width of 100 pixels:

  Point extent = label.computeSize(100, SWT.DEFAULT, false);

For our PictureLabel widget, we could be fancy and stack the image over the text when the width is too small, and/or wrap the text in order to meet a width request, but for simplicity we have decided not to do so. Still, we need to honour the hints. So, our widget will clip. The easiest way to do this is to perform the calculation and then filter the results.

  public Point computeSize(int wHint, int hHint, boolean changed) {
     int width = 0, height = 0;
     if (image != null) {
         Rectangle bounds = image.getBounds();
         width = bounds.width + 5;
         height = bounds.height;
     }
     if (text != null) {
         GC gc = new GC(this);
         Point extent = gc.stringExtent(text);
         gc.dispose();
         width += extent.x;
         height = Math.max(height, extent.y);
     }
     if (wHint != SWT.DEFAULT) width = wHint;
     if (hHint != SWT.DEFAULT) height = hHint;         
     return new Point(width + 2, height + 2);     
  }

Notice that we do not return the hint sizes exactly as specified. We have added the 1-pixel border. Why do we do this? All widgets have a client area and trim. The hint parameters specify the desired size of the client area. We must set the size of the widget so that the size of the client area is the same as the hint, so the size we return from computeSize must include the trim.

What about the changed flag? This is used in conjunction with SWT layout managers and is ignored for basic widgets. This will be discussed when we talk about compound widgets.
Compound Widget Example
Now we will recode the PictureLabel widget as a compound widget. Note that this section assumes that you have read the basic widget example section. This time the widget will be implemented using two Label children: one to display the image, and one to display the text. Since we are using other widgets to implement our widget, we subclass Composite.

public class PictureLabel extends Composite {
  Label image, text;
  Color white;
  
  PictureLabel(Composite parent, int style) {
     super(parent, style);
     white = new Color(null, 255, 255, 255);
     image = new Label(this, 0);
     text = new Label(this, 0);
     setBackground(white);
     image.setBackground(white);
     text.setBackground(white);
     addDisposeListener(new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
            PictureLabel.this.widgetDisposed(e);
         }
     });

As well as initializing the graphics resources in the constructor, we need to create the child widgets and set their background color. A common mistake is to create the child widgets as children of the parent. This would make them peers of our widget. Instead, make sure to create them as children of this. The dispose listener frees the color, as before.

Now that we have handled creation and destruction, we need to lay out the children. There are two possibilities:
·        position the children when the widget is resized
·        use a layout manager

We will implement both here for comparison.
Positioning Children on Resize
First, we will position the children when the widget is resized. We need to add a resize listener.

     addControlListener(new ControlAdapter() {
         public void controlResized(ControlEvent e) {
            PictureLabel.this.controlResized(e);
         }
     });
  }

  void controlResized(ControlEvent e) {
     Point iExtent = image.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     Point tExtent = text.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     image.setBounds(1, 1, iExtent.x, iExtent.y);
     text.setBounds(iExtent.x + 5, 1, tExtent.x, tExtent.y);
  }

When the widget is resized, we compute the size of each of our children, and then use their extents and our 5-pixel spacing and 1-pixel margin to position the children using setBounds.

Now we will write the set and get methods. Because we are not drawing the image and text, damaging the widget will not cause the correct behavior. The children must be resized to show their new contents. To do this, we will take the code from the resize listener and move it into a helper method called resize.

  void controlResized(ControlEvent e) {
     resize();
  }
  
  void resize() {
     Point iExtent = image.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     Point tExtent = text.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     image.setBounds(1, 1, iExtent.x, iExtent.y);
     text.setBounds(iExtent.x + 5, 1, tExtent.x, tExtent.y);
  }

Here are the set and get methods.

  public Image getImage() {
     return image.getImage();
  }

  public void setImage(Image image) {
     this.image.setImage(image);
     resize();
  }

  public String getText() {
     return text.getText();
  }

  public void setText(String text) {
     this.text.setText(text);
     resize();
  }

Now we have to implement the computeSize method. This is a simple matter of asking the children for their preferred sizes.

  public Point computeSize(int wHint, int hHint, boolean changed) {
     Point iExtent = image.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     Point tExtent = text.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     int width = iExtent.x + 5 + tExtent.x;
     int height = Math.max(iExtent.y, tExtent.y);
     if (wHint != SWT.DEFAULT) width = wHint;
     if (hHint != SWT.DEFAULT) height = hHint;         
     return new Point(width + 2, height + 2);
  }

Positioning Children With a Layout Manager
Now we will rewrite our compound widget example using a layout manager to position our widget’s children. We could just use an existing SWT layout manager - RowLayout - to position the children, but we promised to explain the changed parameter in the computeSize method. This also gives an example of how this might be done for more complicated layout requirements. In the code that follows, the class PictureLabelLayout extends Layout, and the rewritten PictureLabel class is listed in its entirety.

The layout manager is set into the widget with the following line of code in the widget constructor:

     setLayout(new PictureLabelLayout());

We will call the layout manager in the widget’s two set methods, with the following line of code:

     layout(true);

The parameter to the layout method is the changed flag. If true, it indicates that the widget contents have changed (as is the case in the two set methods), therefore any caches that the layout manager may have been keeping need to be flushed. When the widget is resized, the SWT system sends layout(false) to the layout manager, so caches do not need to be flushed. This lets the layout manager perform any expensive calculations only when necessary.

In class PictureLabelLayout, we know that composite.getChildren() will always return exactly two children. In general, a layout manager will have to handle any number of children, so if you are implementing a widget that can have an arbitrary number of children you will need to loop through them to do your calculations. Note that it is in this class that we check the value of the changed flag and optionally flush our two “extent” caches.

Notice that the PictureLabel class has been simplified by using a layout manager. The code in computeSize and resize has been moved to the PictureLabelLayout class, and the resize listener is no longer needed.

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;

class PictureLabelLayout extends Layout {
  Point iExtent, tExtent; // the cached sizes

  protected Point computeSize(Composite composite, int wHint, int hHint,
     boolean changed) {
     Control [] children = composite.getChildren();
     if (changed || iExtent == null || tExtent == null) {
         iExtent = children[0].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
         tExtent = children[1].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     }
     int width = iExtent.x + 5 + tExtent.x;
     int height = Math.max(iExtent.y, tExtent.y);
     return new Point(width + 2, height + 2);
  }

  protected void layout(Composite composite, boolean changed) {
     Control [] children = composite.getChildren();
     if (changed || iExtent == null || tExtent == null) {
         iExtent = children[0].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
         tExtent = children[1].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     }
     children[0].setBounds(1, 1, iExtent.x, iExtent.y);
     children[1].setBounds(iExtent.x + 5, 1, tExtent.x, tExtent.y);
  }
}

public class PictureLabel extends Composite {
  Label image, text;
  Color white;
  
  PictureLabel(Composite parent, int style) {
     super(parent, style);
     white = new Color(null, 255, 255, 255);
     image = new Label(this, 0);
     text = new Label(this, 0);
     setBackground(white);
     text.setBackground(white);
     image.setBackground(white);
     addDisposeListener(new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
            PictureLabel.this.widgetDisposed(e);
         }
     });
     setLayout(new PictureLabelLayout());
  }
  
  void widgetDisposed(DisposeEvent e) {
     white.dispose();
  }
     
  public Image getImage() {
     return image.getImage();
  }

  public void setImage(Image image) {
     this.image.setImage(image);
     layout(true);
  }

  public String getText() {
     return text.getText();
  }

  public void setText(String text) {
     this.text.setText(text);
     layout(true);
  }
}
Events and Listeners
Often, you will want a new widget to support an event. For example, you may want your widget to notify listeners when the user selects it. Or you may have an editable widget that should notify listeners when its value has changed.

The details to implement an event called AnEvent are exactly the same as implementing a Java Bean listener:
·        create a class called AnEvent which extends java.util.EventObject and may have additional fields related to the event. Usually you want to provide get methods for event fields, but you do not always want to provide set methods. Fields are typically set in the constructor.
·        create a class called AnEventListener which implements the java.util.EventListener interface and provides a method called, say, anEventHappened(AnEvent event)
·        keep a Vector (or some other collection) of AnEventListener’s in your widget class
·        implement addAnEventListener which adds the specified listener to the Vector
·        implement removeAnEventListener to remove the specified listener from the Vector
·        determine when the event happens in your widget (possibly by adding listeners to your widget) and when it does:
·        create an instance of AnEvent called event, initialized as appropriate
·        send anEventHappened(event) to each of the AnEventListener’s in the Vector

Say we want PictureLabel widgets to notify listeners when the user clicks the left mouse button in the image. We create class ImageClickedEvent with x and y fields, and interface ImageClickedListener with method imageClicked(ImageClickedEvent event).

public class ImageClickedEvent extends java.util.EventObject {
  public int x, y;

  public ImageClickedEvent(Object source, int x, int y) {
     super(source);
     this.x = x;
     this.y = y;
  }
}

public interface ImageClickedListener extends java.util.EventListener {
  public void imageClicked(ImageClickedEvent event);
}

We add a Vector to PictureLabel to store the listeners:

  Vector imageClickedListeners = new Vector();

  public void addImageClickedListener(ImageClickedListener listener) {
     imageClickedListeners.addElement(listener);
  }
  
  public void removeImageClickedListener(ImageClickedListener listener) {
     imageClickedListeners.removeElement(listener);
  }

Finally, in PictureLabel’s constructor, we add a mouse listener to the image Label widget, which does the work of notifying the listeners when the left mouse button is clicked over the image.

     …
     addMouseListener(new MouseAdapter() {
         public void mouseDown(MouseEvent event) {
            if (event.button == 1) {
                PictureLabel.this.mouseDown(event);
            }
         }
     });

  public void mouseDown(MouseEvent event) {
     ImageClickedEvent e = new ImageClickedEvent(this, event.x, event.y);
     int size = imageClickedListeners.size();
     for (int i = 0; i
#include
#include

We will start with the createControl method. Recall that we decided to use a Windows UpDown control. If we create an Edit control first, and then create the UpDown control with UDS_AUTOBUDDY and UDS_SETBUDDYINT flags set, then the Edit control will automatically be associated with the UpDown control’s arrows. We can retrieve the Edit control by sending UDM_GETBUDDY to the UpDown control. We will show you the complete createControl method after we explain how to call in to Java.

Calling in to Java:
The first time we ever call createControl, we initialize some static variables:

static DWORD tlsIndex = 0;
static jobject javaClass;
static jmethodID mid;
static WNDPROC oldProc;

We use one of them (tlsIndex) as a flag to make sure we initialize them only once. Here is the initialization code from createControl:

  if (tlsIndex == 0) {
     tlsIndex = TlsAlloc();
     if (tlsIndex == -1) return (jint) 0;
     javaClass = (*env)->NewGlobalRef(env, (jobject) that);
     mid = (*env)->GetStaticMethodID(env, (jobject) that, "widgetSelected", "(I)V");
     oldProc = (WNDPROC) GetWindowLong((HWND) hwndParent, GWL_WNDPROC);
  }
  TlsSetValue(tlsIndex, (LPVOID) env);

These variables are needed to implement call-in. As this is important code, we will describe each variable that is initialized:

     tlsIndex = TlsAlloc();
     if (tlsIndex == -1) return (jint) 0;
     …
  }
  TlsSetValue(tlsIndex, (LPVOID) env);

Here, we allocate a Windows Thread Local Storage (TLS) index, and then (for each Spinner) we use the TLS index to store a pointer called env. Notice that JNIEnv *env is the first parameter passed to every JNI method. It is a pointer to a function table, and it is only valid in the thread associated with it. We know that we need to call in to Java when the user changes the Spinner value, and that we will be calling in from a Windows ‘window procedure’ or WNDPROC. The WNDPROC does not know about the Java environment. When the WNDPROC is invoked, we will need ‘env’. So we have to save it on creation so that we have it when we need to call in. We also need the class and method ID to call in to, and we can store these in statics because they will be the same across all threads:

     javaClass = (*env)->NewGlobalRef(env, (jobject) that);
     mid = (*env)->GetStaticMethodID(env, (jobject) that, "widgetSelected", "(I)V");

When the user changes the Spinner value, the UpDown sends a WM_VSCROLL to its parent control.  In order to see this message, it is necessary to “subclass the window proc” of the parent.  In Windows, this means replacing the window proc of the parent with our own window proc.  The new WNDPROC will look for WM_VSCROLL (in order to notify the Java code that the Spinner value has been changed) and then call the previous WNDPROC to handle other messages that our control is not interested in.  Note that it is important to call the previous WNDPROC, or the parent window will not behave properly (i.e. it will not paint or resize, etc.) We store the previous WNDPROC in oldProc:

     oldProc = (WNDPROC) GetWindowLong((HWND) hwndParent, GWL_WNDPROC);

The last line in createControl before we return the new handle installs a WNDPROC called WindowProc:

  SetWindowLong((HWND) hwndParent, GWL_WNDPROC, (long) WindowProc);

Here is the code for our WindowProc. First we retrieve env from Thread Local Storage and check if an exception has occurred. Then we see if this is an “UpDown value changed” event (a WM_VSCROLL message with SB_THUMBPOSITION in the low order bits of wParam). If it is, we use env to call in to the Java static method called “widgetSelected”, passing lParam as the handle of the UpDown control. Otherwise, we just forward to the parent control’s window procedure.


LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  JNIEnv *env = TlsGetValue(tlsIndex);
  if (env != NULL) {
     /* If an exception has already occurred,
      * allow the stack to unwind so that the
      * exception will be thrown in Java. */
     if ((*env)->ExceptionOccurred(env)) return 0;
     switch (msg) {
         case WM_VSCROLL:
            if ((wParam & 0xFFFF) == SB_THUMBPOSITION) {
                return (LRESULT) ((*env)->CallStaticIntMethod(env, javaClass, mid, lParam));
            }
            break;
     }
  }
  return CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
}


And finally, here is the code for createControl:

JNIEXPORT jint JNICALL Java_spinner_Spinner_createControl
  (JNIEnv *env, jclass that, jint hwndParent)
{
  HWND hwndText, hwndUpDown;
  if (tlsIndex == 0) {
     tlsIndex = TlsAlloc();
     if (tlsIndex == -1) return (jint) 0;
     javaClass = (*env)->NewGlobalRef(env, (jobject) that);
     mid = (*env)->GetStaticMethodID(env, (jobject) that, "widgetSelected", "(I)V");
     oldProc = (WNDPROC) GetWindowLong((HWND) hwndParent, GWL_WNDPROC);
  }
  TlsSetValue(tlsIndex, (LPVOID) env);
  
  hwndText = CreateWindowEx(
     WS_EX_CLIENTEDGE,
     "EDIT",
     NULL,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP,
     0, 0, 0, 0,
     (HWND) hwndParent,
     0,
     GetModuleHandle(NULL),
     NULL);
  if (hwndText == 0) return (jint) 0;
  hwndUpDown = CreateWindowEx(
     0,
     UPDOWN_CLASS,
     NULL,
     WS_CHILD | WS_VISIBLE | UDS_AUTOBUDDY | UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS,
     0, 0, 0, 0,
     (HWND) hwndParent,
     0,
     GetModuleHandle(NULL),
     NULL);
  if (hwndUpDown == 0) return (jint) 0;
  SetWindowLong((HWND) hwndParent, GWL_WNDPROC, (long) WindowProc);
  return (jint) hwndUpDown;
}

The set and get methods we need to implement are much simpler than the createControl and WindowProc methods. Here are setPosition and getPosition. They simply send the UDM_SETPOS or UDM_GETPOS message to the UpDown handle. The remaining set and get methods are similar, and they are listed in Appendix C: Spinner for Windows. The only interesting one is setFont, which sets the font of the Edit control, which it gets by sending UDM_GETBUDDY to the UpDown handle.

JNIEXPORT void JNICALL Java_spinner_Spinner_setPosition
  (JNIEnv *env, jclass that, jint hwnd, jint position)
{
  SendMessage((HWND) hwnd, UDM_SETPOS, 0, position);
}

JNIEXPORT jint JNICALL Java_spinner_Spinner_getPosition
  (JNIEnv *env, jclass that, jint hwnd)
{
  return (jint) SendMessage((HWND) hwnd, UDM_GETPOS, 0, 0) & 0xFFFF;
}

The resizeControl method positions the Edit control (the buddy) and the UpDown (the arrow buttons) using the specified coordinates and size. For the width of the arrow buttons, we use the width of a typical vertical scrollbar.

JNIEXPORT void JNICALL Java_spinner_Spinner_resizeControl
  (JNIEnv *env, jclass that, jint hwndUpDown, jint x, jint y, jint width, jint height)
{
  HWND hwndText = (HWND) SendMessage((HWND) hwndUpDown, UDM_GETBUDDY, 0, 0);
  UINT flags = SWP_NOZORDER | SWP_DRAWFRAME | SWP_NOACTIVATE;
  int upDownWidth = GetSystemMetrics(SM_CXVSCROLL);
  SetWindowPos(hwndText, (HWND) 0, x, y, width - upDownWidth + 2, height, flags);
  SetWindowPos((HWND) hwndUpDown, (HWND) 0, x + width - upDownWidth, y, upDownWidth, height, flags);         
}

The final method we need to write is computeSize. This is typically a complex method, and our computeSize is no exception. We construct a string of digits the same length as the maximum value, and measure its height and width if drawn in the Edit control’s font. We make sure our control is no shorter than a combo box, and we add in text margins, and the width of the arrow buttons. In order to return the computed height and width values in the result array, we need to lock down the array using the JNI function GetIntArrayElements to protect it from moving as a result of garbage collection.

JNIEXPORT void JNICALL Java_spinner_Spinner_computeSize
  (JNIEnv *env, jclass that, jint hwndUpDown, jintArray result) {
  int width, height;
  TEXTMETRIC tm;
  RECT rect;
  int comboHeight;
  int max, digits;
  UINT flags;
  char text[64];
  HWND hwndText = (HWND) SendMessage((HWND) hwndUpDown, UDM_GETBUDDY, 0, 0);
  HDC hDC = GetDC(hwndText);
  HFONT oldFont = 0;
  HFONT newFont = (HFONT) SendMessage(hwndText, WM_GETFONT, 0, 0);

  jint *result1 = NULL;
  result1 = (*env)->GetIntArrayElements(env, result, NULL);

  if (newFont != 0) oldFont = SelectObject(hDC, newFont);
  GetTextMetrics(hDC, &tm);
  comboHeight = GetSystemMetrics(SM_CYVSCROLL);
  height = (comboHeight > tm.tmHeight) ? comboHeight : tm.tmHeight;
  max = SendMessage((HWND) hwndUpDown, UDM_GETRANGE, 0, 0) & 0xFFFF;
  if (max > 0) {
     digits = 0;
     while (max > 0) {
         text[digits] = '0';
         max /= 10;
         digits++;
     }
     flags = DT_CALCRECT | DT_EDITCONTROL | DT_NOPREFIX;
     DrawText(hDC, (LPCTSTR) text, digits, (LPRECT) &rect, flags);
     width = rect.right - rect.left + 3;
  } else {
     width = 10;
  }
  if (newFont != 0) SelectObject(hDC, oldFont);
  ReleaseDC(hwndText, hDC);
  width += GetSystemMetrics(SM_CXVSCROLL);
  SendMessage(hwndText, EM_GETRECT, 0, (LPARAM) &rect);
  if (rect.top == 0) rect.top = 1; // windows bug fix
  width += (rect.left + 1) * 2;
  height += (rect.top + 1) * 2;
     
  result1 [0] = width;
  result1 [1] = height;
  (*env)->ReleaseIntArrayElements(env, result, result1, 0);
}

The full source listing for the Windows C code and makefile are in Appendix C: Spinner for Windows. A batch file sets environment variables and calls make to create the DLL. Options for your compiler and linker may differ, but you will have to link in the win32 libs: comctl32.lib, user32.lib, and gdi32.lib.

Motif Native Code
Now we need to write “spinner.c” for Motif. In this section, we will only point out the differences between the Motif “spinner.c” and the Windows one. The full source listing for the Motif C code and makefile are in Appendix D: Spinner for Motif. A shell script sets environment variables and calls make.

The Motif equivalent to Thread Local Storage is called Thread-Specific Data (TSD), and its functions are defined in pthread.h. You will need to include at least the following files:

#include
#include
#include

In order to store the env pointer in Thread-Specific Data, you first create a key:

static pthread_key_t envKey;
     …
     pthread_key_create(&envKey, NULL);

and then you store into and retrieve from TSD as follows:

  pthread_setspecific(envKey, env);
  JNIEnv *env = (JNIEnv *) pthread_getspecific(envKey);

As you compare the Windows and Motif “spinner.c” listings, you will notice that the JNI portions of the code are identical: method templates, the use of JNI functions like GetIntArrayElements and ReleaseIntArrayElements for locking/releasing an array of integers, and NewGlobalRef, GetStaticMethodID, and CallStaticIntMethod to call in to Java.

The platform code, however, is completely different. On Motif, we create the native control using XmCreateSimpleSpinBox. The Text widget is created automatically and stored in the XmNtextField resource of the SimpleSpinBox. You can retrieve the Text (for setting the font or computing the preferred size) using:

  Arg arg;
  Widget handleText;
  XtSetArg(arg, XmNtextField, &handleText);
<p class=Code s

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/4906/showart_17827.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP