Monday, November 19, 2012

Creating an IStream from a resource

In Win32, suppose you have an application resource (added with the resource compiler) and you want to open an OLE IStream interface on it. One reason might be to load an image with GDI+.
I initially made my own IStream implementation to provide read-only access to the resource data but I eventually decided to do the following, which is less code but does make a copy of the resource. The API is 'inspired by' the Shell's CreateStreamOnHGlobal function.

HRESULT CreateStreamOnResource(LPCTSTR name, LPSTREAM* stream) {
 HINSTANCE hInst = GetModuleHandle(0);
 return CreateStreamOnResource(hInst, name, stream);
}

HRESULT CreateStreamOnResource(HINSTANCE hInst, LPCTSTR name, LPSTREAM* stream)
{
 assert(hInst != 0);
 assert(name != 0);
 assert(stream != 0);
 *stream = NULL;
 HRSRC hC = FindResource(hInst, name, RT_RCDATA);
 if (hC) {
  // This is not really a HGLOBAL http://msdn.microsoft.com/en-us/library/windows/desktop/ms648046(v=vs.85).aspx
  HGLOBAL hG = LoadResource(hInst, hC);
  if (hG) {
   void* bytes = LockResource(hG);
   ULONG size = SizeofResource(hInst, hC);
   // Create a new empty stream.
   HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, stream);
   if (SUCCEEDED(hr)) {
    ULONG written;
    // Copy the resource into it.
    hr = (*stream)->Write(bytes, size, &written);
   }
   return hr;
  }
 }
 return E_INVALIDARG;
}
It should have been even easier - just get an HGLOBAL with LoadResource and pass it to CreateStreamOnHGlobal but LoadResource does not really return a handle to global memory so I needed to make the additional steps of creating a new stream, locking the resource and copying its data. I think I remember in the Win16 days you really did have to worry about loading and unloading resources from the segmented .exe but these days I think it all just gets mapped anyway.

Thursday, November 1, 2012

ListView and custom Cells

If you want to display a 'complex object' in a ListView, specify it as a parameter, for example
ListView<Thing> listView = new ListView<>();
However this only displays the result of calling Thing.toString() on the objects. To show more complex information you need to customise the ListCell. Start by providing a factory which creates new, customised cells.
listView.setCellFactory(new Callback<ListView<Thing>, ListCell<Thing>>() {
 @Override
 public ListCell<Thing> call(ListView<Thing> p) {
  return new ThingCell();
 }
});
JavaFX maintains some kind of relationship between ListCells and items in the list; it seems to be roughly one cell per visible row, not one cell per item in the list. You need to override updateItem() in Cell to attach a list item to a cell.
public class ThingCell extends ListCell<Thing> {
 @Override
 protected void updateItem(Thing t, boolean isblank) {
  super.updateItem(t, isblank);
  ...
 }
}
In other words, JavaFX uses the factory to create some cells, then attaches the list's contents to them. As the list is scrolled, different objects will be attached in turn. Bear in mind, t may be null - either because you've added a null object to the list, or because JavaFX wants a cell to suitable for displaying an empty row. In the latter case, isblank will be true. The appearance of the cell may be the same for either, in which case you can ignore isblank.
When an object is attached, remember to bind its properties to the cell, instead of just setting them. If the item then changes 'internally', the list will update automatically. For example, suppose Thing has a title property. If you do this:
protected void updateItem(Thing t, boolean isblank) {
 super.updateItem(t, isblank);
 if (t != null) {
  setText(t.titleProperty().get());
 }
 else {
  setText("");
 }
}
the item will display correctly when it is added to the list (and also if JavaFX re-attaches the objects), but if someone calls thing.titleProperty().set("New text") the list will not change. Instead, try
protected void updateItem(Thing t, boolean isblank) {
 super.updateItem(t, isblank);
 if (t != null) {
  textProperty().bind(t.titleProperty());
 }
 else {
  textProperty().unbind();
  textProperty().set("");
 }
}
Of course, if the list items never change after they've been added to the list, it isn't necessary to create the bindings and the first method can be used.