Welcome to MSDN Blogs Sign in | Join | Help

Type.MakeXxxType() (or The Intricacies of Reflection) - a non-NETCF post

The other day someone dropped into my office and asked about the Type.MakeByRefType() method.  Neither I nor my other visitor are experts in reflection on .Net, but we investigated and found the following:

Imagine this situation:

public class foo {
    // Do something with an int.
    public void bar(Int32 anArgument) { ... }

    // Do something to an int and modify it.
    public void bar(ref Int32 aRefArgument) { ... }

    // Do something multiple times in one call.
    public void bar(Int32[] anArrayArgument) { ... }
}

Now say you want to find bar() on type foo via reflection:

    MethodInfo mi = typeof(foo).GetMethod("bar");

To specify the version of bar you want, you need GetMethod(string name, Type[] types).  Okay, so getting the first version is straightforward:

    MethodInfo mi = typeof(foo).GetMethod("bar", new Type[] { typeof(Int32) });

What if you want the second of third overload of bar()?  Well, now you need Type.MakeByRefType() or Type.MakeArrayType() like this:

    MethodInfo mi = typeof(foo).GetMethod("bar", new Type[] { typeof(Int32).MakeByRefType() });

or

    MethodInfo mi = typeof(foo).GetMethod("bar", new Type[] ( typeof(Int32).MakeArrayType() });

Similarly, you can use Type.MakeGenericType() and/or Type.MakePointerType() for other method signatures.

Here's a simple sample:

using System;
using System.Reflection;

namespace SAMPLE
{
    public class foo
    {
        public foo() {}

        public void bar(int arg)
        {
            Console.WriteLine("bar(int arg):     " + arg.ToString());
        }

        public void bar(ref int arg)
        {
            Console.WriteLine("bar(ref int arg): " + arg.ToString());
            arg *= arg;
            Console.WriteLine("                  " + arg.ToString());
        }

        public void bar(int[] args)
        {
            Console.WriteLine("bar(int[] args):  ");
            for (int iArg = 0; iArg < args.Length; iArg++)
                Console.WriteLine("                 " + args[iArg].ToString());
        }
    }

    public class MakeXxxType
    {
        static int arg = 1968;
        static int refArg = 256;
        static int[] argArray = new int[] { 1970, 1995, 2000 };

        public static int Main()
        {
            foo f = new foo();

            MethodInfo mi = f.GetType().GetMethod("bar", new Type[] { typeof(Int32) });
            mi.Invoke(f, new object[] { arg });

            mi = f.GetType().GetMethod("bar", new Type[] { typeof(Int32).MakeByRefType() });
            mi.Invoke(f, new object[] { refArg });

            mi = f.GetType().GetMethod("bar", new Type[] { typeof(Int32).MakeArrayType() });
            mi.Invoke(f, new object[] { argArray });

            return 0;
        }
    }
}

By the way, Type.MakeByRefType(), Type.MakeArrayType() and Type.MakePointerType() are not supported in the .net Compact Framework (yet?).  However, Type.MakeGenericType() is supported.

Posted by clorton | 0 Comments

Fonts, LogFonts and the Compact Framework (NETCF)

As you may know, much of System.Drawing on the desktop/full framework is based on GDI+.  Because GDI+ is not implemented on PocketPC/Smartphone/WindowsMobile, the functionality of System.Drawing in the Compact Framework is somewhat constrained.

As we were planning and adding features to V2 of the Compact Framework, there were requests for the ability to draw rotated text from managed code (one might like to align street names with the street in a map, for example).  On the desktop, this is done by setting calling Graphics.RotateTransform() prior to calling Graphics.DrawString().  Since SetWorldTransform is not supported in GWES on WindowsCE (and WindowsCE based devices), Graphics.RotateTransform() is not available on the Compact Framework.  So, how would we enable rotated text in the Compact Framework?

The answer is found in the LOGFONT structure and CreateFontIndirect().  Well, actually it's found in Font.FromLogFont() method.

Note #1: On the desktop the Font.FromLogFont comes in two flavors, FromLogFont(object) and FromLogFont(object, IntPtr).  This was too painful for the Compact Framework team (what does it mean to call Font.FromLogFont(12)?) so on the Compact Framework the first argument to FromLogFont must be a Microsoft.WindowsCE.Forms.LogFont object even though the method signature matches the desktop: FromLogFont(object).

After you find Font.FromLogFont and Microsoft.WindowsCE.Forms.LogFont, it is straightforward to create a font which includes rotation and will render at an angle when used in a call to Graphics.DrawString().

Note #2a: Escapement and Orientation cannot be set independently and should be set to the same value per MSDN documentation on the LOGFONT structure.

Note #2b: The rotation fields of LOGFONT (and the LogFont class), escapement and orientation, are measured in tenths of degrees - i.e. specifiy 45 degrees of rotation with a value of 450.

Another tidbit to know about fonts on devices is that the device wide setting for ClearType will, if ClearType is enabled, override whatever value you use in the Quality field of your LogFont object.

Note #3: If ClearType is enabled system wide on your device, that setting will override the value in the Quality property of your LogFont object used in FromLogFont().

Finally, one more piece of information from the MSDN documentation is relevant here - the Height property of the LogFont object may be positive or negative.  If the value is positive, e.g. 12, this is matched against the cell height of available fonts.  If the value is negative, e.g. -12, the absolute value of the height is matched against the character height of available fonts.  The standard formula for calculating the value the height property of the LogFont object from point size is the following:

height = -MulDiv(pointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72) where MulDiv(a, b, c) is (((a)*(b))/(c)).

Graphics.DpiY can be substituted for GetDeviceCaps(hDC, LOGPIXELSY) in managed code.

I've attached a basic LogFontExplorer project which allows control over most of the properties of LogFont and the ability to change the system wide ClearType setting from inside the application.  The text rendering is buffered to a bitmap so the difference between quality levels with and without system wide ClearType can be discerned.

Also, just for comparison, here's a snippet of code showing how to do rotated text on the desktop framework (I've included this project in the attachment as well).

Font fnt = new Font("Tahoma", 12.0f);

...

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
   
using (Graphics g = this.CreateGraphics())
   
{
        g.TranslateTransform(e.X, e.Y);
        g.RotateTransform(((
float)numericUpDown1.Value) / 10.0f);
        g.DrawString(
"Hello, world!", fnt, new SolidBrush(Color.Blue), 0.0f, 0.0f);
    }
}

 

Keyboard Navigation on PocketPC with the Compact Framework

We recently had a customer let us know that the difference between keyboard navigation on PocketPC and Smartphone was causing them trouble.  I thought I would take little time to work up a workaround for this issue.  I'll present a solution using the recommended managed methods first and then an alternative which plays with native interop to the SendInput() API.

The basic problem is that the direction pad (d-pad) can be used to navigate through a form of controls on Smartphone but not on PocketPC.  The d-pad on both devices act like the arrow keys of a standard keyboard and sends VK_UP, VK_DOWN, VK_LEFT and VK_RIGHT inputs to the system when used.  ComboBox, DateTimePicker and TextBox are examples of controls which capture and respond to the up and down arrows on PocketPC (Tab and Shift-Tab still navigate as expected and stylus input to the touchscreen, if present, will switch focus between controls).

The recommended solution is to hook the KeyDown event (or override OnKeyDown) to capture Keys.Down and Keys.Up and use the SelectNextControl() method on the Control class (which calls the same code as the internal tabbing navigation) to navigate to the next/previous control in the tab order.

Here's an excerpt from SmartphoneStyleNavigationWithSNC.zip which contains an instance of each of the troublesome controls, ComboBox, DateTimePicker and TextBox:

// Excerpted from InitializeComponent() in SampleForm.Designer.cs

this.comboBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HandleKeyDown);
this.dateTimePicker1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HandleKeyDown);
this.textBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HandleKeyDown);

// Custom code in SampleForm.cs

private void HandleKeyDown(object sender, KeyEventArgs e)
{
    switch (e.KeyData)
    {
        case Keys.Up:
        case Keys.Down:
            e.Handled =
true;
            this.SelectNextControl((Control)sender, e.KeyData == Keys.Down, true, true, true);
            break;

        default:
            break;
    }
}

Something to note above is that we can call SelectNextControl() directly because HandleKeyDown() is implemented on a form, a top level window.  If you decide to create custom control classes from ComboBox, DateTimePicker and/or TextBox which override OnKeyDown, you will need to use the following code:

this.TopLevelControl.SelectNextControl((Control)sender, e.KeyData == Keys.Down, true, true, true);

Just for kicks I looked into eating the arrow key messages and putting [Shift-]Tab key messages into the message queue.  There's an official API, SendInput(), to do this.  Here are the relevant parts of that code (note that you still need to hook the KeyDown events of the controls in question to HandleKeyDown):

public class WinCE
{
    // The unused private fields below are required to match the native structure layout.
#pragma warning disable 0169
   
public struct KEYBOARDINPUT
   
{
       
public uint type;
       
public ushort wVk;
       
ushort wScan;
       
public uint dwFlags;
       
uint time;
       
uint dwExtraInfo;
       
uint unused1;
       
uint unused2;
    }
#pragma warning restore 0169

   
public const uint INPUT_KEYBOARD = 1;
   
public const uint KEYEVENTF_KEYUP = 2;
   
public const ushort VK_TAB = 0x0009;
   
public const ushort VK_SHIFT = 0x0010;

    [
DllImport("coredll.dll", SetLastError = true)]
   
public static extern uint SendInput(uint cInputs, /* [MarshalAs(UnmanagedType.LPArray)] */ KEYBOARDINPUT[] inputs, int cbSize);
}

private void HandleKeyDown(object sender, KeyEventArgs e)
{
   
WinCE.KEYBOARDINPUT[] inputs;
   
uint retVal;
   
int error;

   
switch (e.KeyData)
    {
       
case Keys.Down:
            e.Handled =
true;
            inputs =
new WinCE.KEYBOARDINPUT[2];
            inputs[0].type = inputs[1].type =
WinCE.INPUT_KEYBOARD;
            inputs[0].wVk = inputs[1].wVk =
WinCE.VK_TAB;
            inputs[1].dwFlags =
WinCE.KEYEVENTF_KEYUP;
            retVal =
WinCE.SendInput(2, inputs, 0x001C);
           
if (retVal != 2)
            {
                error =
Marshal.GetLastWin32Error();
               
throw new Exception(string.Format("SendInput() returned {0}.", error));
            }
           
break;

        
case Keys.Up:
            e.Handled =
true;
            inputs =
new WinCE.KEYBOARDINPUT[4];
            inputs[0].type = inputs[1].type = inputs[2].type = inputs[3].type =
WinCE.INPUT_KEYBOARD;
            inputs[0].wVk = inputs[3].wVk =
WinCE.VK_SHIFT;
            inputs[1].wVk = inputs[2].wVk =
WinCE.VK_TAB;
            inputs[2].dwFlags = inputs[3].dwFlags =
WinCE.KEYEVENTF_KEYUP;
            retVal =
WinCE.SendInput(4, inputs, 0x001C);
           
if (retVal != 4)
            {
                error =
Marshal.GetLastWin32Error();
               
throw new Exception(string.Format("SendInput() returned {0}.", error));
            }
           
break;

        
default:
           
break;
    }
}

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by clorton | 0 Comments

Attachment(s): SmartphoneStyleNavigation.zip

Alphablending with NETCF

Not long after V1 of the Compact Framework went out we started getting questions about support (or lack thereof) for advanced graphics and drawing functions available on the desktop framework but missing from the Compact Framework.  For V1 we made a decision based on both size and performance to implement System.Windows.Forms and System.Drawing over the native functionality of WindowsCE/PocketPC/Smartphone.  Since there is no version of GDI+ on these platforms, System.Drawing was limited to GDI (actually GWES) functionality and lacked a number of the more sophisticated features such as image rotation and coordinate scaling (among other things).

However, with the advent of Windows Mobile 5.0 we have reason for some rejoicing: alpha blending.  This ability allows some interesting effects which can really spiff up an application.

Briefly, there are two ways to do alpha blending in WM5: the AlphaBlend() function and with the Image COM object in the Imaging API.

First, AlphaBlend().

The AlphaBlend function has the ability to do either constant alpha blending over the entire source image or pixel level blending for bitmaps with an alpha channel.  Unfortunately, the NETCF bitmap loader creates a bitmap with the same pixel format as the display when loading a bitmap file or resource and loses the alpha channel information (if present).  This means that the AlphaBlend function can only be used for constant value alpha blending of images.

Here is a set of declarations for using AlphaBlend through p/invoke:

public struct BlendFunction
{
    public byte BlendOp;
    public byte BlendFlags;
    public byte SourceConstantAlpha;
    public byte AlphaFormat;
}

public enum BlendOperation : byte
{
    AC_SRC_OVER = 0x00
}

public enum BlendFlags : byte
{
    Zero = 0x00
}

public enum SourceConstantAlpha : byte
{
    Transparent = 0x00,
    Opaque = 0xFF
}

public enum AlphaFormat : byte
{
    AC_SRC_ALPHA = 0x01
}

public class PlatformAPIs
{
    [
DllImport("coredll.dll")]
    extern public static Int32 AlphaBlend(IntPtr hdcDest,
                                         
Int32 xDest,
                                         
Int32 yDest,
                                         
Int32 cxDest,
                                         
Int32 cyDest,
                                         
IntPtr hdcSrc,
                                         
Int32 xSrc,
                                         
Int32 ySrc,
                                         
Int32 cxSrc,
                                         
Int32
cySrc,
                                          BlendFunction blendFunction);

}

Here's an example of using the function inside an override of OnPaint():

// Load the image to use with the AlphaBlend API.
string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
constantAlphaImage =
new Bitmap(path + @"\blendme.bmp");

// AlphaBlend takes two HDC's - one source and one destination. Here's the source.

using
(Graphics gxSrc = Graphics.FromImage(constantAlphaImage))
{
    IntPtr
hdcDst = e.Graphics.GetHdc();
    IntPtr hdcSrc = gxSrc.GetHdc();
    BlendFunction blendFunction = new BlendFunction();
    blendFunction.BlendOp = (
byte)BlendOperation.AC_SRC_OVER; // Only supported blend operation
    blendFunction.BlendFlags = (byte)BlendFlags.Zero; // Documentation says put 0 here
    blendFunction.SourceConstantAlpha = (byte)128;    // Constant alpha factor
    blendFunction.AlphaFormat = (byte)0;              // Don't look for per pixel alpha
    PlatformAPIs.AlphaBlend(hdcDst, left, top, width, height, hdcSrc, 0, 0, width, height, blendFunction);
    gxSrc.ReleaseHdc(hdcSrc);      // Required cleanup to GetHdc()
    e.Graphics.ReleaseHdc(hdcDst); // Required cleanup to GetHdc()
}

Next, the Image object (or IImage interface) from the Imaging API.

If we instantiate an IImagingFactory and use it to load our image from a file or resource, the alpha channel will be preserved (don't, however, confuse the IImage COM interface we get back with a System.Drawing.Image managed object).  Then we can ask the Imaging object to draw itself and it will use the alpha channel information in the image during the render.

Here is the declaration of the enums and COM interfaces:

// Pulled from gdipluspixelformats.h in the Windows Mobile 5.0 Pocket PC SDK
public enum PixelFormatID :
int
{
    PixelFormatIndexed = 0x00010000, 
// Indexes into a palette
    PixelFormatGDI = 0x00020000,      
// Is a GDI-supported format
   
PixelFormatAlpha = 0x00040000,   
// Has an alpha component
   
PixelFormatPAlpha = 0x00080000,  
// Pre-multiplied alpha
   
PixelFormatExtended = 0x00100000,
// Extended color 16 bits/channel
   
PixelFormatCanonical = 0x00200000,
    PixelFormatUndefined = 0,
    PixelFormatDontCare = 0,
    PixelFormat1bppIndexed = (1 | ( 1 << 8) | PixelFormatIndexed | PixelFormatGDI),
    PixelFormat4bppIndexed = (2 | ( 4 << 8) | PixelFormatIndexed | PixelFormatGDI),
    PixelFormat8bppIndexed = (3 | ( 8 << 8) | PixelFormatIndexed | PixelFormatGDI),
    PixelFormat16bppRGB555 = (5 | (16 << 8) | PixelFormatGDI),
    PixelFormat16bppRGB565 = (6 | (16 << 8) | PixelFormatGDI),
    PixelFormat16bppARGB1555 = (7 | (16 << 8) | PixelFormatAlpha | PixelFormatGDI),
    PixelFormat24bppRGB = (8 | (24 << 8) | PixelFormatGDI),
    PixelFormat32bppRGB = (9 | (32 << 8) | PixelFormatGDI),
    PixelFormat32bppARGB = (10 | (32 << 8) | PixelFormatAlpha | PixelFormatGDI | PixelFormatCanonical),
    PixelFormat32bppPARGB = (11 | (32 << 8) | PixelFormatAlpha | PixelFormatPAlpha | PixelFormatGDI),
    PixelFormat48bppRGB = (12 | (48 << 8) | PixelFormatExtended),
    PixelFormat64bppARGB = (13 | (64 << 8) | PixelFormatAlpha | PixelFormatCanonical | PixelFormatExtended),
    PixelFormat64bppPARGB = (14 | (64 << 8) | PixelFormatAlpha | PixelFormatPAlpha | PixelFormatExtended),
    PixelFormatMax = 15
}

// Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK
public enum BufferDisposalFlag :
int
{
    BufferDisposalFlagNone,
    BufferDisposalFlagGlobalFree,
    BufferDisposalFlagCoTaskMemFree,
    BufferDisposalFlagUnmapView
}

// Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK
public enum InterpolationHint :
int
{
    InterpolationHintDefault,
    InterpolationHintNearestNeighbor,
    InterpolationHintBilinear,
    InterpolationHintAveraging,
    InterpolationHintBicubic
}

// Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK
public struct
ImageInfo
{
    // I am being lazy here, I don't care at this point about the RawDataFormat GUID
    public uint GuidPart1;

   
public uint GuidPart2;

   
public uint GuidPart3;

   
public uint GuidPart4;

   
public
PixelFormatID pixelFormat;
    public uint
Width;
    public uint
Height;
    public uint
TileWidth;
    public uint
TileHeight;
    public double
Xdpi;
    public double
Ydpi;
    public uint
Flags;
}

// Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK
[ComImport, Guid("327ABDA7-072B-11D3-9D7B-0000F81EF32E"), InterfaceType(ComInterfaceType
.InterfaceIsIUnknown)]
[ComVisible(true
)]
public interface
IImagingFactory
{
    uint CreateImageFromStream();
// This is a place holder
   
uint CreateImageFromFile(string filename, out
IImage image);
   
// We need the MarshalAs attribute here to keep COM interop from sending the buffer down as a Safe Array.
   
uint CreateImageFromBuffer([MarshalAs(UnmanagedType.LPArray)] byte[] buffer, uint size, BufferDisposalFlag disposalFlag, out
IImage image);
    uint CreateNewBitmap(
);            // This is a place holder
   
uint CreateBitmapFromImage();      // This is a place holder
   
uint CreateBitmapFromBuffer();     // This is a place holder
   
uint CreateImageDecoder();        
// This is a place holder
   
uint CreateImageEncoderToStream();
// This is a place holder
   
uint CreateImageEncoderToFile();  
// This is a place holder
   
uint GetInstalledDecoders();      
// This is a place holder
   
uint GetInstalledEncoders();      
// This is a place holder
   
uint InstallImageCodec();         
// This is a place holder
   
uint UninstallImageCodec();       
// This is a place holder
}

// Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK
[ComImport, Guid("327ABDA9-072B-11D3-9D7B-0000F81EF32E"), InterfaceType(ComInterfaceType
.InterfaceIsIUnknown)]
[ComVisible(true
)]
public interface
IImage
{
    uint GetPhysicalDimension(out
Size size);
    uint GetImageInfo(out
ImageInfo info);
    uint SetImageFlags(uint
flags);
    // "Correct" declaration: uint Draw(IntPtr hdc, ref Rectangle dstRect, ref Rectangle srcRect);
    uint Draw(IntPtr hdc, ref Rectangle dstRect, IntPtr NULL);
    uint PushIntoSink();
// This is a place holder
   
uint GetThumbnail(uint thumbWidth, uint thumbHeight, out
IImage thumbImage);
}

Here is how to load an image from a file:

string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
// Load the image with alpha data through Imaging.
IImagingFactory factory = (IImagingFactory)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("327ABDA8-072B-11D3-9D7B-0000F81EF32E"
)));
IImage imagingImage;
factory.CreateImageFromFile(path +
@"\ihavealpha.png", out
imagingImage);

Here is how to load an image from an embedded resource:

string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
IImagingFactory factory = (IImagingFactory)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("327ABDA8-072B-11D3-9D7B-0000F81EF32E")));
// Load the image with alpha data from an embedded resource through Imaging
// !! If your data source is not a MemoryStream, you will need to get your image data into a byte array and use it below. !!
MemoryStream strm = (MemoryStream)Assembly.GetExecutingAssembly().GetManifestResourceStream("AlphaExample.embedded.png");
byte[] pbBuf = strm.GetBuffer();
uint cbBuf = (uint)strm.Length;
IImage imagingResource;
factory.CreateImageFromBuffer(pbBuf, cbBuf, BufferDisposalFlag.BufferDisposalFlagNone,
out imagingResource);

Here is how to render the image during the OnPaint():

// The bitmap needs to be created with the 32bpp pixel format for the IImage to do the right thing.
using (Bitmap backBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb))
{
    using
(Graphics gxBuffer = Graphics.FromImage(backBuffer))
    {
       
gxBuffer.Clear(this.BackColor);
        IntPtr
hdcDest = gxBuffer.GetHdc();
        Rectangle dstRect = new Rectangle(left, top, right, bottom);
        // Ask the Image object from Imaging to draw itself.
        imagingImage.Draw(hdcDest,
ref dstRect, IntPtr.Zero);
        gxBuffer.ReleaseHdc(hdcDest);
        // Put the final composed image on screen.
       
e.Graphics.DrawImage(backBuffer, 0, 0);
    }
}

I've attached a simple WM5 PocketPC project for VS 2005 which includes several images (two .PNGs with alpha data) and demonstrates the use of AlphaBlend() and the Imaging API.  Please note that there is no hardware acceleration of alpha blending on devices so use this functionality prudently or expect to notice an effect on your rendering performance.

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by clorton | 7 Comments

Attachment(s): AlphaExample.zip
 
Page view tracker