I guess this is generic issue and not limited to ComboBox, however I have specifically problem with ComboBox. I extended ComboBox object with MyCB MyCB : ComboBox)
What happens is every time I hover over the control, leave the control, expand selection box or select a value, the control flickers. For a short while I can see default (non-replaced) control which is being instantly replaced with mine.
I believe what's happening is that Windows first draws the "original" control (by calling base.WndProc()) and then repaints it with mine.
The question is, can I somehow stop windows from painting it's own control and instantly paint mine?
Below is code overriding WndProc
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics gg = this.CreateGraphics();
gg.FillRectangle(BorderBrush, this.ClientRectangle);
// ... //
//Draw the arrow
gg.FillPath(ArrowBrush, pth);
// ... //
if(this.Text == "")
gg.DrawString("-- SELECT --", this.Font, new SolidBrush(Color.Black), rf, sf);
else
gg.DrawString(this.Text, this.Font, new SolidBrush(Color.Black), rf, sf);
gg.Dispose();
}
}
What have I tried so far:
I know that I can't do this:
if (m.Msg == WM_PAINT)
{
...
}
else
{
base.WndProc(ref m);
}
as that will cause control to repaint itself infinitely (not sure why)
I was able to remove flickering which happened when mouse leaves/enters the control by adding this code
if (m.Msg == WM_MOUSEFIRST || m.Msg == WM_MOUSELEAVE) // 0x0200 0x02A3
{
m.Result = (IntPtr)1;
}
else
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
...
}
}
however this doesn't solve the problem completely
I looked into ILSpy to check on ComboBox's WndProc but there were so many windows messages that I didn't know which of those I could possibly immitate to achive my goal
public CustomComboBox()
{
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == Win32.WM_MOUSEFIRST || m.Msg == Win32.WM_MOUSELEAVE || m.Msg == Win32.WM_MOUSEHOVER) //0x0200, 0x02A3, 0x02A1
{
m.Result = (IntPtr)1;
return;
}
base.WndProc(ref m);
if (m.Msg == Win32.WM_PAINT)
{
IntPtr hDC = Win32.GetWindowDC(m.HWnd);
Graphics g = Graphics.FromHdc(hDC);
g.SmoothingMode = SmoothingMode.HighSpeed;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
if (Text.Length == 0)
{
StringFormat stringFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
g.DrawString("Water Marks", Font, new SolidBrush(Color.FromArgb(51, 51, 51)), (Bounds.Width - 2), FontHeight + 2, stringFormat);
}
Pen border = new Pen(ui_BorderLineColor, 1);
g.DrawRectangle(border, 0, 0, this.Width - 1, this.Height - 1);
g.Dispose();
Win32.ReleaseDC(m.HWnd, hDC);
}
}
protected override void InitLayout()
{
base.InitLayout();
DropDownStyle = ComboBoxStyle.DropDownList; //Works only in DropDownList
}
// Edit completing Moses comment
public class Win32
{
public const int WM_MOUSEFIRST = 0x0200;
public const int WM_MOUSELEAVE = 0x02A3;
public const int WM_MOUSEHOVER = 0x02A1;
public const int WM_PAINT = 0x000F;
[DllImport("user32")]
public static extern IntPtr GetWindowDC(IntPtr hwnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
[DllImport("user32.dll")]
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprc, IntPtr hrgn, uint flags);
}
Related
I want recolor only the windows form's title bar of my application, I found this code online
[DllImport("user32.dll")]
static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
protected override void WndProc(ref System.Windows.Forms.Message m)
{
const int WM_NCPAINT = 0x85;
base.WndProc(ref m);
if (m.Msg == WM_NCPAINT)
{
IntPtr hdc = GetWindowDC(m.HWnd);
if ((int)hdc != 0)
{
Graphics g = Graphics.FromHdc(hdc);
g.FillRectangle(Brushes.Green, 10, 0, 4800, 23);
g.Flush();
ReleaseDC(m.HWnd, hdc);
}
}
}
But I don't know how to trigger it, Could you help me?
What I did was to set the FormBorderStyle property to None, then I used a flowLayoutPanel to used as as my title bar,setting mouse events and addinng minimize/close button.
i have a Windows Forms application which should be borderless (even without the titlebar) and be resizeable and moveable.
so far, i've set the BorderStyle to 'none' which removes all the borders and let my program look pretty.
Now i've added invisble borders with the following:
private const int cGrip = 16; // Grip size
private const int cCaption = 50; // Caption bar height;
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, this.ClientSize.Height - cGrip, cGrip, cGrip);
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
rc = new Rectangle(0, 0, this.ClientSize.Width, cCaption);
//e.Graphics.FillRectangle(Brushes.DarkBlue, rc);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84)
{ // Trap WM_NCHITTEST
Point pos = new Point(m.LParam.ToInt32());
pos = this.PointToClient(pos);
if (pos.Y < cCaption)
{
m.Result = (IntPtr)2; // HTCAPTION
return;
}
if (pos.X >= this.ClientSize.Width - cGrip && pos.Y >= this.ClientSize.Height - cGrip)
{
m.Result = (IntPtr)17; // HTBOTTOMRIGHT
return;
}
}
base.WndProc(ref m);
}
Program Layout
The blue rectangle is rendered via the OnPaint() method and shows the field where the user is able to move the window while holding the left mouse button.
My problem is, that this rectangle is below my label.
Does anyone know how to get the rectangle in front of the label?
Another way would be disabling the label which than turns dark grey.
You would solve my problem if i could change the color of my disabled label.
Leave the Label enabled and make it so that dragging the label causes the form to be dragged as well:
public const int HT_CAPTION = 0x2;
public const int WM_NCLBUTTONDOWN = 0xA1;
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private void label1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(this.Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
I've made a custom TextBox class to have a text box with custom border in my application, based on this other SO post. If I set any of the new custom properties in the form designer, they appear momentarily, until I change the control focus, and when I run the application, the new border settings are not displayed. I did update my form's InitializeComponent method, so that the text box initializes a new BorderedTextBox instead of TextBox. Does anyone know what's wrong here?
public class BorderedTextBox : TextBox
{
private Color _borderColor = Color.Black;
private int _borderWidth = 2;
private int _borderRadius = 5;
public BorderedTextBox() : base()
{
InitializeComponent();
this.Paint += this.BorderedTextBox_Paint;
}
public BorderedTextBox(int width, int radius, Color color) : base()
{
this._borderWidth = Math.Max(1, width);
this._borderColor = color;
this._borderRadius = Math.Max(0, radius);
InitializeComponent();
this.Paint += this.BorderedTextBox_Paint;
}
public Color BorderColor
{
get => this._borderColor;
set
{
this._borderColor = value;
DrawTextBox();
}
}
public int BorderWidth
{
get => this._borderWidth;
set
{
if (value > 0)
{
this._borderWidth = Math.Min(value, 10);
DrawTextBox();
}
}
}
public int BorderRadius
{
get => this._borderRadius;
set
{ // Setting a radius of 0 produces square corners...
if (value >= 0)
{
this._borderRadius = value;
this.DrawTextBox();
}
}
}
private void BorderedTextBox_Paint(object sender, PaintEventArgs e) => DrawTextBox(e.Graphics);
private void DrawTextBox() => this.DrawTextBox(this.CreateGraphics());
private void DrawTextBox(Graphics g)
{
Brush borderBrush = new SolidBrush(this.BorderColor);
Pen borderPen = new Pen(borderBrush, (float)this._borderWidth);
Rectangle rect = new Rectangle(
this.ClientRectangle.X,
this.ClientRectangle.Y,
this.ClientRectangle.Width - 1,
this.ClientRectangle.Height - 1);
// Clear text and border
g.Clear(this.BackColor);
// Drawing Border
g.DrawRoundedRectangle(
borderPen,
(0 == this._borderWidth % 2) ? rect.X + this._borderWidth / 2 : rect.X + 1 + this._borderWidth / 2,
rect.Y,
rect.Width - this._borderWidth,
(0 == this._borderWidth % 2) ? rect.Height - this._borderWidth / 2 : rect.Height - 1 - this._borderWidth / 2,
(float)this._borderRadius);
}
#region Component Designer generated code
/// <summary>Required designer variable.</summary>
private System.ComponentModel.IContainer components = null;
/// <summary>Clean up any resources being used.</summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
/// <summary>Required method for Designer support - Don't modify!</summary>
private void InitializeComponent() => components = new System.ComponentModel.Container();
#endregion
}
You need to override WndProc:
private const int WM_PAINT = 0x000F;
protected override void WndProc( ref Message m ) {
if(m.Msg == WM_PAINT ) {
base.WndProc( ref m );
Graphics gr = this.CreateGraphics();
//draw what you want
gr.Dispose();
return;
}
base.WndProc( ref m );
}
Works fine without any issues. It draws in client area though. A complete version drawing a custom border, textbox need to have border:
[DllImport( "user32.dll" )]
static extern IntPtr GetWindowDC( IntPtr hWnd );
[DllImport( "user32.dll" )]
static extern bool ReleaseDC( IntPtr hWnd, IntPtr hDC );
[DllImport( "gdi32.dll" )]
static extern bool FillRgn( IntPtr hdc, IntPtr hrgn, IntPtr hbr );
[DllImport( "gdi32.dll" )]
static extern IntPtr CreateRectRgn( int nLeftRect, int nTopRect, int nRightRect,
int nBottomRect );
[DllImport( "gdi32.dll" )]
static extern IntPtr CreateSolidBrush( uint crColor );
[DllImport( "gdi32.dll" )]
static extern bool DeleteObject( IntPtr hObject );
private const int WM_NCPAINT = 0x0085;
private const int WM_PAINT = 0x000F;
private const int RGN_DIFF = 0x4;
private int p_border = 2;
protected override void WndProc( ref Message m ) {
if(m.Msg == WM_PAINT ) {
base.WndProc( ref m );
IntPtr hdc = GetWindowDC( this.Handle ); //gr.GetHdc();
IntPtr rgn = CreateRectRgn( 0, 0, this.Width, this.Height );
IntPtr brush = CreateSolidBrush( 0xFF0000 ); //Blue : B G R
CombineRgn( rgn, rgn, CreateRectRgn( p_border, p_border, this.Width - p_border,
this.Height - p_border ), RGN_DIFF );
FillRgn( hdc, rgn, brush );
ReleaseDC( this.Handle, hdc );
DeleteObject( rgn );
DeleteObject( brush );
m.Result = IntPtr.Zero;
return;
}
if( m.Msg == WM_NCPAINT ) {
return;
}
base.WndProc( ref m );
}
Winforms TextBox is a legacy control I think even from way back before the .Net framework.
It doesn't support owner-drawing and as one can see on MSDN neither Paint not OnPaint are documented to work.
Yes, you can code them, and yes, they will have some effect. But TextBox doesn't play by the normal rules and will mess up your drawing without triggering a paint event.
Possibly you can hook yourself into the windows message queue (WndProc) but it is generally not recommended, especially for something like adorning it with a border.
Usually nesting a TextBox in a Panel and letting the Panel draw a nice Border is the simplest solution..
I have to color the TitleBar of DockWindow. I can able to color the normal winform Titlebar color..but I don't know how to change the color of dockwindow titlebar if it is docked right,left,top,bottom.
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
const int WM_NCPAINT = 0x85;
if (m.Msg == WM_NCPAINT)
{
IntPtr hdc = GetWindowDC(m.HWnd);
if ((int)hdc != 0)
{
Graphics g = Graphics.FromHdc(hdc);
g.FillRectangle(Brushes.Green, new Rectangle(0, 0, 4800, 23));
//(0, 0, 400, 252));
g.Flush();
ReleaseDC(m.HWnd, hdc);
}
}
}
By using this i can able to paint the titlebar of Normal winform..But i cant do this same for Dockwindow
Use Drawing Custom Borders in Windows Forms project from codeplex. This project is a small library that extends Windows Forms with ability to customize the windows's non-client area.
I have a label which labels the line numbers based on the text on RichTextBox. I have hooked the event of Vscroll to handle the labeling.
private void rtbLogicCode_VScroll(object sender, EventArgs e)
{
Point pt = new Point(0, 1);
int firstIndex = rtbLogicCode.GetCharIndexFromPosition(pt);
int firstLine = rtbLogicCode.GetLineFromCharIndex(firstIndex);
pt.X = ClientRectangle.Width;
pt.Y = ClientRectangle.Height;
int lastIndex = rtbLogicCode.GetCharIndexFromPosition(pt);
int lastLine = rtbLogicCode.GetLineFromCharIndex(lastIndex);
// Small correction
if (rtbLogicCode.Text.EndsWith("\n"))
lastLine++;
labelLogicCode.ResetText();
LabelLineNum(firstLine+1,lastLine);
}
#endregion
private void LabelLineNum(int startNum, int lastNum)
{
labelLogicCode.Font = UIConstant.DDCLogicCodeFont;
for (int i = startNum; i < lastNum; i++)
{
labelLogicCode.Text += i + Environment.NewLine;
}
}
Everything seems to work properly except RichTextBox uses Smooth Scrolling feature, which screws up my line numbering in many cases where the user has not scrolled all the way to the next line. This causes the line numbers to be not synchronized with the actual text shown on the RichTextBox.
In the end, I need to disable smoothscrolling feature to accomplish this. I was told that you can override the postMessage API of RichTextBox to disable the mentioned feature but after searching through many documents, I couldn't find any good ones.
I would appreciate a solution that is as detailed as possible on how to disable smoothscrolling feature. Thanks.
Here's a VB example from Microsoft, suggesting you need to intercept WM_MOUSEWHEEL messages.
Here's a quick prototype in C#:
class MyRichTextBox : RichTextBox {
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(
IntPtr hWnd, // handle to destination window
uint Msg, // message
IntPtr wParam, // first message parameter
IntPtr lParam // second message parameter
);
const uint WM_MOUSEWHEEL = 0x20A;
const uint WM_VSCROLL = 0x115;
const uint SB_LINEUP = 0;
const uint SB_LINEDOWN = 1;
const uint SB_THUMBTRACK = 5;
private void Intercept(ref Message m) {
int delta = (int)m.WParam >> 16 & 0xFF;
if((delta >> 7) == 1) {
SendMessage(m.HWnd, WM_VSCROLL, (IntPtr)SB_LINEDOWN, (IntPtr)0);
} else {
SendMessage(m.HWnd, WM_VSCROLL, (IntPtr)SB_LINEUP, (IntPtr)0);
}
}
protected override void WndProc(ref Message m) {
switch((uint)m.Msg) {
case WM_MOUSEWHEEL:
Intercept(ref m);
break;
case WM_VSCROLL:
if(((uint)m.WParam & 0xFF) == SB_THUMBTRACK) {
Intercept(ref m);
} else {
base.WndProc(ref m);
}
break;
default:
base.WndProc(ref m);
break;
}
}
}
I know this is old, but if Dan Sporici's site ever goes down I thought I post his fantastic solution. It's simple and works easily with just a copy & paste.
class editedRichTextBox : RichTextBox
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
//this message is sent to the control when we scroll using the mouse
private const int WM_MOUSEWHEEL = 0x20A;
//and this one issues the control to perform scrolling
private const int WM_VSCROLL = 0x115;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
{
int scrollLines = SystemInformation.MouseWheelScrollLines;
for (int i = 0; i < scrollLines; i++)
{
if ((int)m.WParam > 0) // when wParam is greater than 0
SendMessage(this.Handle, WM_VSCROLL, (IntPtr)0, IntPtr.Zero); // scroll up
else
SendMessage(this.Handle, WM_VSCROLL, (IntPtr)1, IntPtr.Zero); // else scroll down
}
return;
}
base.WndProc(ref m);
}
}
This will solve for VB.NET as well as C# (use code converter).
Just paste this code outside your own class (Eg: Public Class Form1).
Run the code once and new control will be added to the toolbox.
Now add the control to your FORM from the toolbox in IDE.
Code:
Public Class MyRichTextBox : Inherits RichTextBox
<System.Runtime.InteropServices.DllImport("user32.dll")> Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = &H20A Then : Dim ud As IntPtr
If m.WParam.ToInt32 > 0 Then ud = 0 Else ud = 1
For i = 1 To 3 : SendMessage(Me.Handle, &H115, ud, 0) : Next
Else : MyBase.WndProc(m) : End If
End Sub
End Class