Does anyone know how the resulting Graphics objects differ ...? What i
really mean is can someone explain it to me please?
A)
[DllImport("user32.dll")] protected static extern IntPtr GetWindowDC
(IntPtr hWnd );
hDC = GetWindowDC(this.Handle);
g_dc = Graphics.FromHdc(hDC);
B)
Graphics g = this.CreateGraphics();
You have my thanks and full attention,
James Randle.
In the WM_NC_PAINT message handler, a graphics object is already
created, but then SendPrintClientMsg() is called which seems to do the
same work again (in reverse - getting the hDC from
this.CreateGraphics). Is this mad, or more likely, am i missing
something?
...
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
namespace DrawFlat
{
[ToolboxBitmap(typeof(System.Windows.Forms.ComboBox))]
public class FlatComboBox: ComboBox
{
#region ComboInfoHelper
internal class ComboInfoHelper
{
[DllImport("user32")]
private static extern bool GetComboBoxInfo(IntPtr hwndCombo, ref
ComboBoxInfo info);
#region RECT struct
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
#endregion
#region ComboBoxInfo Struct
[StructLayout(LayoutKind.Sequential)]
private struct ComboBoxInfo
{
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public IntPtr stateButton;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
#endregion
public static int GetComboDropDownWidth()
{
ComboBox cb = new ComboBox();
int width = GetComboDropDownWidth(cb.Handle);
cb.Dispose();
return width;
}
public static int GetComboDropDownWidth(IntPtr handle)
{
ComboBoxInfo cbi = new ComboBoxInfo();
cbi.cbSize = Marshal.SizeOf(cbi);
GetComboBoxInfo(handle, ref cbi);
int width = cbi.rcButton.Right - cbi.rcButton.Left;
return width;
}
}
#endregion
public const int WM_ERASEBKGND = 0x14;
public const int WM_PAINT = 0xF;
public const int WM_NC_PAINT = 0x85;
public const int WM_PRINTCLIENT = 0x318;
private static int DropDownButtonWidth = 17;
[DllImport("user32.dll", EntryPoint="SendMessageA")]
public static extern int SendMessage (IntPtr hwnd, int wMsg, IntPtr
wParam, object lParam);
[DllImport("user32")]
public static extern IntPtr GetWindowDC (IntPtr hWnd );
[DllImport("user32")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC );
static FlatComboBox()
{
DropDownButtonWidth = ComboInfoHelper.GetComboDropDownWidth() + 2;
}
public FlatComboBox()
: base()
{
this.SetStyle(ControlStyles.DoubleBuffer, true);
}
protected override void OnSelectedValueChanged(EventArgs e)
{
base.OnSelectedValueChanged (e);
this.Invalidate();
}
protected override void WndProc(ref Message m)
{
if (this.DropDownStyle == ComboBoxStyle.Simple)
{
base.WndProc(ref m);
return;
}
IntPtr hDC = IntPtr.Zero;
Graphics gdc = null;
switch (m.Msg)
{
case WM_NC_PAINT:
hDC = GetWindowDC(this.Handle);
gdc = Graphics.FromHdc(hDC);
SendMessage(this.Handle, WM_ERASEBKGND, hDC, 0);
SendPrintClientMsg(); // send to draw client area
PaintFlatControlBorder(this, gdc);
m.Result = (IntPtr) 1; // indicate msg has been processed
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
break;
case WM_PAINT:
base.WndProc(ref m);
// flatten the border area again
hDC = GetWindowDC(this.Handle);
gdc = Graphics.FromHdc(hDC);
Pen p = new Pen((this.Enabled? BackColor:SystemColors.Control),
2);
gdc.DrawRectangle(p, new Rectangle(2, 2, this.Width-3,
this.Height-3));
PaintFlatDropDown(this, gdc);
PaintFlatControlBorder(this, gdc);
ReleaseDC(m.HWnd, hDC);
gdc.Dispose();
break;
default:
base.WndProc(ref m);
break;
}
}
private void SendPrintClientMsg()
{
// We send this message for the control to redraw the client area
Graphics gClient = this.CreateGraphics();
IntPtr ptrClientDC = gClient.GetHdc();
SendMessage(this.Handle, WM_PRINTCLIENT, ptrClientDC, 0);
gClient.ReleaseHdc(ptrClientDC);
gClient.Dispose();
}
private void PaintFlatControlBorder(Control ctrl, Graphics g)
{
Rectangle rect = new Rectangle(0, 0, ctrl.Width, ctrl.Height);
if (ctrl.Focused == false || ctrl.Enabled == false )
ControlPaint.DrawBorder(g, rect, SystemColors.ControlDark,
ButtonBorderStyle.Solid);
else
ControlPaint.DrawBorder(g, rect, Color.Black,
ButtonBorderStyle.Solid);
}
public static void PaintFlatDropDown(Control ctrl, Graphics g)
{
Rectangle rect = new Rectangle(ctrl.Width-DropDownButtonWidth, 0,
DropDownButtonWidth, ctrl.Height);
ControlPaint.DrawComboButton(g, rect, ButtonState.Flat);
}
protected override void OnLostFocus(System.EventArgs e)
{
base.OnLostFocus(e);
this.Invalidate();
}
protected override void OnGotFocus(System.EventArgs e)
{
base.OnGotFocus(e);
this.Invalidate();
}
protected override void OnResize(EventArgs e)
{
base.OnResize (e);
this.Invalidate();
}
}
}
I don't know. The whole thing does look kind of odd to me...presumably, the
code is like this because some of the code was originally unmanaged? I
can't imagine writing C# code like this from scratch.
From http://msdn2.microsoft.com/en-us/library/ms535950.aspx it's apparent to
me that FromHdc does what you'd think: it just wraps a device context handle
with a Graphics object.
I don't know how CreateGraphics works, but presumably it calls either GetDC
or GetWindowDC. Okay, I admit it could call CreateDC but that seems less
likely for screen drawing. Whether it calls GetDC or GetWindowDC probably
depends on whether the Control and/or Form class always uses an "own DC" for
the window.
So yes, it does kind of look like the same DC may be getting handed back and
forth to multiple Graphics objects, and yes it does seem to me that would be
wasteful.
Ain't legacy code great? :)
Pete
FromHdc using the handle from GetWindowDC will return a graphics object
based on the whole window including the non-client area. This DC is normally
only used by the system unless you want to do clever stuff like override the
non client drawing.
You should really use niether. See the GDI+ FAQ for how and when to get a
Graphics object.
--
Bob Powell [MVP]
Visual C#, System.Drawing
Ramuseco Limited .NET consulting
http://www.ramuseco.com
Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm
Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm
All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.
"pigeonrandle" <pigeon...@hotmail.com> wrote in message
news:1165194940.2...@80g2000cwy.googlegroups.com...
Thankyou both for your replies.
Bob,
If i might ask, why do you think the author of the above code used this
method? I am about to read the GDI+ FAQ, but are there some appropriate
questions i should be asking that would hasten my successful completion
of a similar control?
I just read that the combobox has no 'non-client' area so can i discard
WM_NC_PAINT all together?
Thanks and thanks again,
James Randle.
Arrggghhh!
James Randle.
placing
this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint,
true);
in the constructor (UserPaint being the important bit) has restored the
missing paint event, and the draw flat button code can be called from
an overriden onpaint().
Thanks to anyone half writing a reply as i write mine, and again thanks
to Bob for pointing me in the right direction.
James Randle.
Now the drop down is a solid black color, and none of the text is
displayed. I now see why the contrived method was used in the first
place ...
it allowed user painting of some of the control whilst letting the text
drawing continue as expected.
Is there a way to accomplish this properly? To be honest, i dont want
to have to write loads of drawing code (that is already there) just
because i want to draw a flat button and flat border which wouldnt
obscure the text anyway.
Any comments?
James Randle