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