在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;



} 

你可能感兴趣的:(C#)