在C#中使用GDI的简单总结

在C#中使用GDI的简单总结

在C#默认支持的是GDI+库,使用GDI+库,有很丰富的函数和排版手段,可以满足大部分的要求.除非,你需要使用bitmap字体,GDI+对于字体的支持有很大限制,支持truetype字体,而对于点阵字体(栅格字体)则不再支持,但是很多字体都是这种点阵字体,这样就带来一个问题,使用不了了.而很多公司则会自己制作某些用途的字体,比如说在LED显示屏上使用,这个时候使用GDI+则不再是一个明智的选择了.此外GDI+虽然强大,但是经过测试发现效率却是低下,速度比GDI慢了不少.

但是CS的界面开发环境和编码习惯又很适合,所以比较折衷的解决方法是使用C#调用系统的gdi api函数实现编程.

首先C#环境中的color结构是AARRGGBB,而在win32的颜色结构却是AABBGGRR,所以如果不注意则会使到颜色出现偏差或者逆转.在.net环境的类库当中,在system.drawing的命名空间中有ColorTranslator.ToWin32()这个函数,来对color结构转换成win32的api使用的颜色结构.

GDI中比较重要的函数,gdi函数基本都是来之一个dll. Gdi32.dll

[DllImport("gdi32.dll")]

public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

[DllImport("GDI32.dll")]

public static extern bool DeleteObject(IntPtr objectHandle);

[DllImport("gdi32.dll")]

public static extern bool FillRgn(IntPtr hdc, IntPtr hrgn, IntPtr hbr);

[DllImport("gdi32.dll")]

public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect,

int nBottomRect);

[DllImport("gdi32.dll")]

public static extern IntPtr CreateSolidBrush(Int32 crColor);

[DllImport("gdi32.dll")]

public static extern int SetBkMode(IntPtr hdc, int iBkMode);

public const int TRANSPARENT = 1;

public const int OPAQUE = 2;

[DllImport("gdi32.dll")]

static extern uint SetBkColor(IntPtr hdc, int crColor);

[DllImport("gdi32.dll")]

static extern uint SetTextColor(IntPtr hdc, int crColor);

[DllImport("gdi32", EntryPoint = "CreateFontW", CharSet = CharSet.Auto)]

static extern IntPtr CreateFontW(

[In] Int32 nHeight,

[In] Int32 nWidth,

[In] Int32 nEscapement,

[In] Int32 nOrientation,

[In] FontWeight fnWeight,

[In] Boolean fdwItalic,

[In] Boolean fdwUnderline,

[In] Boolean fdwStrikeOut,

[In] FontCharSet fdwCharSet,

[In] FontPrecision fdwOutputPrecision,

[In] FontClipPrecision fdwClipPrecision,

[In] FontQuality fdwQuality,

[In] FontPitchAndFamily fdwPitchAndFamily,

[In] String lpszFace);

[DllImport("gdi32.dll")]

public static extern int GetTextFace(IntPtr hdc, int nCount,

[Out] StringBuilder lpFaceName);

public const Int32 LF_FACESIZE = 32;

[DllImport("gdi32.dll", ExactSpelling = true)]

public static extern bool BitBlt(

IntPtr hdcDest, // 目标设备的句柄

int nXDest, // 目标对象的左上角的X坐标

int nYDest, // 目标对象的左上角的Y坐标

int nWidth, // 目标对象的矩形的宽度

int nHeight, // 目标对象的矩形的长度

IntPtr hdcSrc, // 源设备的句柄

int nXSrc, // 源对象的左上角的X坐标

int nYSrc, // 源对象的左上角的X坐标

TernaryRasterOperations dwRop // 光栅的操作值

);

[DllImport("gdi32.dll")]

public static extern bool StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest,

int nWidthDest, int nHeightDest,

IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc,

TernaryRasterOperations dwRop);

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool GetTextExtentPoint(IntPtr hdc, string lpString,

int cbString, ref Size lpSize);

[DllImport("Gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC lptm);

[DllImport("gdi32.dll")]

public static extern bool GetCharABCWidthsFloatW(IntPtr hdc, uint iFirstChar, uint iLastChar, [Out] ABCFloat[] lpABCF);

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool TextOutW(IntPtr hdc, int nXStart, int nYStart,

string lpString, int cbString);

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool GetCharWidth32(IntPtr hdc, uint iFirstChar, uint iLastChar,

[Out] int[] lpBuffer);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]

public static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Rect lpRect, dwDTFormat wFormat);

[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]

static extern IntPtr CreateCompatibleDC(IntPtr hdc);

[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]

static extern bool DeleteDC(IntPtr hdc);

这些都是在GDI中比较常用的函数,其中SelectObject,和DeleteObject更是关键,在GDI的使用中最容易出现的问题就是内存泄漏,很多时候是因为没有正确释放资源引起的.所以需要特别小心,在GDI中释放资源使用DeleteObject这个函数来释放.

下面来实现一些具体的函数

///  

/// 填充特定DC的一个区域的特定颜色 

///  

/// 给定DC 

/// 给定区域 

/// 给定颜色 

public static void FillRect(IntPtr hdc, Rectangle Rect, Color FillColor) 

{ 

IntPtr fillBrush = CreateSolidBrush(ColorTranslator.ToWin32(FillColor)); 

IntPtr rectR = CreateRectRgn(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom); 

FillRgn(hdc, rectR, fillBrush); 

DeleteObject(rectR); 

DeleteObject(fillBrush); 

}

这个函数实现对一个区域填充一个颜色, 当中创建了画笔,创建了区域,然后最后,当然还要记得释放两者占用的资源.

创建字体函数

public static IntPtr CreatFont(String FontName, Int32 Height, FontStyle Style) 

{ 

IntPtr Result;// = IntPtr.Zero; 

FontWeight boldWeight = FontWeight.FW_NORMAL; 

Boolean Italic = false; 

Boolean Underline = false; 

Boolean Bold = false; 

if ((Style & FontStyle.Bold) != 0) 

{ 

Bold = true; 

} 

if ((Style & FontStyle.Italic) != 0) 

{ 

Italic = true; 

} 

if ((Style & FontStyle.Underline) != 0) 

{ 

Underline = true; 

} 

if (Bold) 

{ 

boldWeight = FontWeight.FW_BOLD; 

} 

Result = CreateFontW(Height, 0, 0, 0, boldWeight, Italic, Underline, false, 

FontCharSet.DEFAULT_CHARSET, FontPrecision.OUT_DEFAULT_PRECIS, 

FontClipPrecision.CLIP_DEFAULT_PRECIS, FontQuality.DRAFT_QUALITY, 

FontPitchAndFamily.DEFAULT_PITCH, FontName); 

return Result; 

}

在.net中,默认的字体类,不支持点阵字体,所以要使用CreateFontW这个函数来创建自己的字体资源,其中大部分的选项都试用默认值即可.

然后就是设置画板字体,在GDI环境中,DC是带着字体的,而不像GDI+那样子是分离的,所以经常需要设置不同的字体

public static IntPtr SetCanvasFont(IntPtr hdc, ApiFont NewFont) 

{ 

IntPtr FontPtr = CreatFont(NewFont.Name, NewFont.Size, NewFont.Style); 

IntPtr OldPtr = SelectObject(hdc, FontPtr); 

DeleteObject(OldPtr); 

return OldPtr; 

}

这个函数,将DC原来的字体资源释放掉,这样的实现会带来一个新的问题,因为一般来说都需要将DC原来的字体资源再通过selectobject函数放回去,然后将新的字体资源释放掉.所以这个函数是要小心使用的.

所以就有了第二个版本

public static IntPtr SetCanvasFontNotDelete( IntPtr hdc, ApiFont NewFont )
{

    IntPtr FontPtr = CreatFont(NewFont.Name, NewFont.Size, NewFont.Style);

    IntPtr OldPtr = SelectObject(hdc, FontPtr);

    return OldPtr;

} 

 
这样子就可以手动的释放资源,但是需要特别注意,的是一定要记得释放掉字体资源.

一般的使用画图的步骤

 IntPtr pTarget = e.Graphics.GetHdc();

IntPtr pSource = CreateCompatibleDC(pTarget);

IntPtr pOrig = SelectObject(pSource, drawBmp.GetHbitmap());

GDIApi.StretchBlt(pTarget, 0, 0, this.Width, this.Height, pSource, 0, 0, drawWidth, drawHeight, TernaryRasterOperations.SRCCOPY);

IntPtr pNew = SelectObject(pSource, pOrig);

DeleteObject(pNew);

DeleteDC(pSource);

e.Graphics.ReleaseHdc(pTarget); 

这样子就可以将drawBmp画到e.graphics上面了.最重要的是后面释放掉资源,否则内存的泄漏速度是很厉害的.我的软件每次重画就有7M左右的泄漏.一下子从十几M的内存上升到几百M的内存占用

[DllImport("gdi32.dll")]

public static extern int SetBkMode( IntPtr hdc, int iBkMode );

public const int TRANSPARENT = 1;

public const int OPAQUE = 2; 

这个函数是设置透明度的,参数2如果为1则是透明,2则是不透明.

不透明的话,将字符串画上去的时候,会有白色的背景.一般来说设为透明.

获取字符或者字体的信息函数

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool GetTextExtentPoint( IntPtr hdc, string lpString,

int cbString, ref Size lpSize );

[DllImport("Gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool GetTextMetrics( IntPtr hdc, out TEXTMETRIC lptm );

[DllImport("gdi32.dll")]

public static extern bool GetCharABCWidthsFloatW( IntPtr hdc, uint iFirstChar, uint iLastChar, [Out] ABCFloat[] lpABCF );

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool GetCharWidth32( IntPtr hdc, uint iFirstChar, uint iLastChar,

[Out] int[] lpBuffer ); 

这几个函数都可以获取字体或者字符串占用的空间大小,而又各有区别.

GetTextExtentPoint可以方便的获取一个字符串,或者字符串的部分的长度,因为可以通过cbString这个长度来控制获取的范围.

GetTextMetrics则是获取一个字体的各种高度信息,包括height,ascent,descent,还包括字体能够表现的字符范围等等信息.

GetCharABCWidthsFloatW则是获取某段连续字符串的abcwidth信息,abcwidth信息在某些情况下,需要特别注意,否则斜体会排版得很难看.

GetCharWidth32获取一个连续的字符段的宽度信息, 但是根据实践,居然和GetTextExtentPoint获取的信息不大一致,暂时是少于实际占用的空间.使到计算出来的占用宽度实际上不足以容纳字符串的排版.

字符串的描绘

这是在gdi操作中非常重要的一部分,使用Gdi是因为需要使用特殊字体,而字体当然是针对字符串来使用的的.所以,这根本就是使用gdi的目的.

常用的字符串输出有来个函数

[DllImport("gdi32.dll", CharSet = CharSet.Auto)]

public static extern bool TextOutW( IntPtr hdc, int nXStart, int nYStart,

string lpString, int cbString );

[DllImport("user32.dll", CharSet = CharSet.Unicode)]

public static extern int DrawText( IntPtr hdc, string lpStr, int nCount, ref Rect lpRect, dwDTFormat wFormat ); 

TextOutW是一个比较简单的函数,适合一般的场合,只需要设置X和Y的坐标即可

DrawText,则会控制输出的空间大小,排版规则.比较适合需要精确控制的场所,又或者比如说输出阿拉伯文字的时候,要设置为右对齐.

获取系统所有的字体

由于C#不支持点阵字体,所以自然地,使用.net提供的函数,获取的安装字体列表自然是不包含点阵字体的.所以并不符合我的要求.所以还得使用系统的api函数,来获取安装字体列表.

private Int32 EnumFontCallBack(ref ENUMLOGFONTEX lpelfe, IntPtr lpntme, int FontType, int lParam) 

{ 

//Debug.WriteLine(lpelfe.elfFullName); 

if (lpelfe.elfFullName.Substring(0, 1) != "@") 

{ 

if (!sysFontList.Contains(lpelfe.elfFullName)) 

{ 

sysFontList.Add(lpelfe.elfFullName); 

} 

} 

return 1; 

} 

LOGFONT logfont = new LOGFONT(); 

logfont.lfCharSet = FontCharSet.DEFAULT_CHARSET; 

Bitmap bmp = new Bitmap(10, 10); 

Graphics g = Graphics.FromImage(bmp); 

IntPtr hDC = g.GetHdc(); 

EnumFontFamilies.EnumFontFamiliesEx(hDC, logfont, EnumFontCallBack, IntPtr.Zero, 0); 

g.ReleaseHdc(hDC); 

g.Dispose(); 

bmp.Dispose(); 

public class EnumFontFamilies 

{ 

public const int LF_FACESIZE = 32; 

public delegate int EnumFontExDelegate( ref ENUMLOGFONTEX lpelfe, IntPtr lpntme, int FontType, int lParam ); 

[DllImport("gdi32.dll", EntryPoint = "EnumFontFamiliesEx", CharSet = CharSet.Unicode)] 

public static extern int EnumFontFamiliesEx( IntPtr hDC, [In] LOGFONT logFont, EnumFontExDelegate enumFontExCallback, 

IntPtr lParam, uint dwFlags ); 

} 

其中EnumFontCallBack为回调函数,通过这个回调函数,可以获取到系统所有的字体,包括点阵的字体.

附录A:

GDI使用的结构和常量

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

public struct ENUMLOGFONTEX
{

    public LOGFONT elfLogFont;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

    public string elfFullName;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

    public string elfStyle;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

    public string elfScript;

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

public class LOGFONT
{

    public int lfHeight;

    public int lfWidth;

    public int lfEscapement;

    public int lfOrientation;

    public FontWeight lfWeight;

    [MarshalAs(UnmanagedType.U1)]

    public bool lfItalic;

    [MarshalAs(UnmanagedType.U1)]

    public bool lfUnderline;

    [MarshalAs(UnmanagedType.U1)]

    public bool lfStrikeOut;

    public FontCharSet lfCharSet;

    public FontPrecision lfOutPrecision;

    public FontClipPrecision lfClipPrecision;

    public FontQuality lfQuality;

    public FontPitchAndFamily lfPitchAndFamily;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]

    public string lfFaceName;

    public override string ToString()
    {

        StringBuilder sb = new StringBuilder();

        sb.Append("LOGFONT\n");

        sb.AppendFormat(" lfHeight: {0}\n", lfHeight);

        sb.AppendFormat(" lfWidth: {0}\n", lfWidth);

        sb.AppendFormat(" lfEscapement: {0}\n", lfEscapement);

        sb.AppendFormat(" lfOrientation: {0}\n", lfOrientation);

        sb.AppendFormat(" lfWeight: {0}\n", lfWeight);

        sb.AppendFormat(" lfItalic: {0}\n", lfItalic);

        sb.AppendFormat(" lfUnderline: {0}\n", lfUnderline);

        sb.AppendFormat(" lfStrikeOut: {0}\n", lfStrikeOut);

        sb.AppendFormat(" lfCharSet: {0}\n", lfCharSet);

        sb.AppendFormat(" lfOutPrecision: {0}\n", lfOutPrecision);

        sb.AppendFormat(" lfClipPrecision: {0}\n", lfClipPrecision);

        sb.AppendFormat(" lfQuality: {0}\n", lfQuality);

        sb.AppendFormat(" lfPitchAndFamily: {0}\n", lfPitchAndFamily);

        sb.AppendFormat(" lfFaceName: {0}\n", lfFaceName);

        return sb.ToString();

    }

}

public enum FontWeight : int
{

    FW_DONTCARE = 0,

    FW_THIN = 100,

    FW_EXTRALIGHT = 200,

    FW_LIGHT = 300,

    FW_NORMAL = 400,

    FW_MEDIUM = 500,

    FW_SEMIBOLD = 600,

    FW_BOLD = 700,

    FW_EXTRABOLD = 800,

    FW_HEAVY = 900,

}

public enum FontCharSet : byte
{

    ANSI_CHARSET = 0,

    DEFAULT_CHARSET = 1,

    SYMBOL_CHARSET = 2,

    SHIFTJIS_CHARSET = 128,

    HANGEUL_CHARSET = 129,

    HANGUL_CHARSET = 129,

    GB2312_CHARSET = 134,

    CHINESEBIG5_CHARSET = 136,

    OEM_CHARSET = 255,

    JOHAB_CHARSET = 130,

    HEBREW_CHARSET = 177,

    ARABIC_CHARSET = 178,

    GREEK_CHARSET = 161,

    TURKISH_CHARSET = 162,

    VIETNAMESE_CHARSET = 163,

    THAI_CHARSET = 222,

    EASTEUROPE_CHARSET = 238,

    RUSSIAN_CHARSET = 204,

    MAC_CHARSET = 77,

    BALTIC_CHARSET = 186,

}

public enum FontPrecision : byte
{

    OUT_DEFAULT_PRECIS = 0,

    OUT_STRING_PRECIS = 1,

    OUT_CHARACTER_PRECIS = 2,

    OUT_STROKE_PRECIS = 3,

    OUT_TT_PRECIS = 4,

    OUT_DEVICE_PRECIS = 5,

    OUT_RASTER_PRECIS = 6,

    OUT_TT_ONLY_PRECIS = 7,

    OUT_OUTLINE_PRECIS = 8,

    OUT_SCREEN_OUTLINE_PRECIS = 9,

    OUT_PS_ONLY_PRECIS = 10,

}

public enum FontClipPrecision : byte
{

    CLIP_DEFAULT_PRECIS = 0,

    CLIP_CHARACTER_PRECIS = 1,

    CLIP_STROKE_PRECIS = 2,

    CLIP_MASK = 0xf,

    CLIP_LH_ANGLES = (1 << 4),

    CLIP_TT_ALWAYS = (2 << 4),

    CLIP_DFA_DISABLE = (4 << 4),

    CLIP_EMBEDDED = (8 << 4),

}

public enum FontQuality : byte
{

    DEFAULT_QUALITY = 0,

    DRAFT_QUALITY = 1,

    PROOF_QUALITY = 2,

    NONANTIALIASED_QUALITY = 3,

    ANTIALIASED_QUALITY = 4,

    CLEARTYPE_QUALITY = 5,

    CLEARTYPE_NATURAL_QUALITY = 6,

}

[Flags]

public enum FontPitchAndFamily : byte
{

    DEFAULT_PITCH = 0,

    FIXED_PITCH = 1,

    VARIABLE_PITCH = 2,

    FF_DONTCARE = (0 << 4),

    FF_ROMAN = (1 << 4),

    FF_SWISS = (2 << 4),

    FF_MODERN = (3 << 4),

    FF_SCRIPT = (4 << 4),

    FF_DECORATIVE = (5 << 4),

}

/// <summary> 

/// Enumeration for the raster operations used in BitBlt. 

/// In C++ these are actually #define. But to use these 

/// constants with C#, a new enumeration type is defined. 

/// </summary> 

public enum TernaryRasterOperations
{

    SRCCOPY = 0x00CC0020, /* dest = source */

    SRCPAINT = 0x00EE0086, /* dest = source OR dest */

    SRCAND = 0x008800C6, /* dest = source AND dest */

    SRCINVERT = 0x00660046, /* dest = source XOR dest */

    SRCERASE = 0x00440328, /* dest = source AND (NOT dest ) */

    NOTSRCCOPY = 0x00330008, /* dest = (NOT source) */

    NOTSRCERASE = 0x001100A6, /* dest = (NOT src) AND (NOT dest) */

    MERGECOPY = 0x00C000CA, /* dest = (source AND pattern) */

    MERGEPAINT = 0x00BB0226, /* dest = (NOT source) OR dest */

    PATCOPY = 0x00F00021, /* dest = pattern */

    PATPAINT = 0x00FB0A09, /* dest = DPSnoo */

    PATINVERT = 0x005A0049, /* dest = pattern XOR dest */

    DSTINVERT = 0x00550009, /* dest = (NOT dest) */

    BLACKNESS = 0x00000042, /* dest = BLACK */

    WHITENESS = 0x00FF0062, /* dest = WHITE */

};

[Flags]

public enum dwDTFormat : int
{

    DT_TOP = 0, DT_LEFT = 0x00000000, DT_CENTER = 0x00000001, DT_RIGHT = 0x00000002,

    DT_VCENTER = 0x00000004, DT_BOTTOM = 0x00000008, DT_WORDBREAK = 0x00000010, DT_SINGLELINE = 0x00000020,

    DT_EXPANDTABS = 0x00000040, DT_TABSTOP = 0x00000080, DT_NOCLIP = 0x00000100, DT_EXTERNALLEADING = 0x00000200,

    DT_CALCRECT = 0x00000400, DT_NOPREFIX = 0x00000800, DT_INTERNAL = 0x00001000

};

public struct Rect
{

    public int Left, Top, Right, Bottom;

    public Rect( Rectangle r )
    {

        this.Left = r.Left;

        this.Top = r.Top;

        this.Bottom = r.Bottom;

        this.Right = r.Right;

    }

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

public struct TEXTMETRIC
{

    public Int32 tmHeight;

    public Int32 tmAscent;

    public Int32 tmDescent;

    public Int32 tmInternalLeading;

    public Int32 tmExternalLeading;

    public Int32 tmAveCharWidth;

    public Int32 tmMaxCharWidth;

    public Int32 tmWeight;

    public Int32 tmOverhang;

    public Int32 tmDigitizedAspectX;

    public Int32 tmDigitizedAspectY;

    public char tmFirstChar;

    public char tmLastChar;

    public char tmDefaultChar;

    public char tmBreakChar;

    public byte tmItalic;

    public byte tmUnderlined;

    public byte tmStruckOut;

    public byte tmPitchAndFamily;

    public byte tmCharSet;

} 
posted @ 2011-07-09 16:56  CanY  Views(9828)  Comments(0Edit  收藏  举报