 |
|
 |
Hello dear fellow users of The Code Project
I got a problem with my richedit control and when I try to subclass it to use the Visual Style using uxtheme its removing the border, but its not painting the new improved border around the control, I wonder what I do wrong I use this article as an example and MSDN #http://msdn.microsoft.com/en-us/library/bb773187(v=VS.85).aspx
I can provide you with code, but that will have to be later today as I'm about to go to work now.
Thanks in advance
Regards
David
|
|
|
|
 |
|
 |
For code based on > .NET Framework 1.1 please use this approach...
Add your control to a panel and...
panel.BackColor = System.Windows.Forms.VisualStyles.VisualStyleInformation.TextControlBorder;
panel.Padding = new Padding(1);
|
|
|
|
 |
|
 |
Hello Bernhard Elbl,
I'm really sorry that I was not able to give you a complete story about my trouble with custom painting using themes and WM_NCPAINT message.
See I develop in C/C++ Win32 (I know wrong section of Codeproject, but please see beside that as you yourself use P/Invoke to archieve your goal with a nice border on your Richedit control.
And I have used severeal other guides to archive Visual Style Themed border, but it just wont.
And I came to the point that I really wanted to talk to another person as this is stressing me out, mostly anything else I have as goals is soon ready but not that part.
But when I find a way to say it in a concise way I will write again.
Thanks alot
and Thanks for an awesome guide
/David
|
|
|
|
 |
|
 |
hi, Elbl. are you still there? it has been a long time since this article was posted.
i tried your code and it works well. but when i made my own themed commandbutton in the same way of yours, but it failed. did i forget anything? pls help me check my code. thanks a lot for your time.
below is my code of VS2003:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace CustomControls
{
public class CustomButton : System.Windows.Forms.Button
{
private System.ComponentModel.Container components = null;
public CustomButton()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
private const int WM_PAINT =0x000F;
public const int WM_NCPAINT = 0x85;
public const int WM_NCCALCSIZE = 0x83;
public const int WM_THEMECHANGED = 0x031A;
private enum BUTTONPARTS:int
{
BP_UNKNOWN = 0,
BP_PUSHBUTTON = 1,
BP_RADIOBUTTON = 2,
BP_CHECKBOX = 3,
BP_GROUPBOX = 4,
BP_USERBUTTON = 5,
BP_COMMANDLINK = 6,
BP_COMMANDLINKGLYPH = 7
};
private enum PUSHBUTTONSTATES:int
{
PBS_NORMAL = 1,
PBS_HOT = 2,
PBS_PRESSED = 3,
PBS_DISABLED = 4,
PBS_DEFAULTED = 5,
PBS_DEFAULTED_ANIMATING = 6
};
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left_, int top_, int right_, int bottom_)
{
Left = left_;
Top = top_;
Right = right_;
Bottom = bottom_;
}
}
[DllImport("uxtheme.dll", ExactSpelling=true)]
public extern static bool IsThemeActive();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC (IntPtr hWnd );
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC );
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("uxtheme.dll", ExactSpelling=true, CharSet=CharSet.Unicode)]
public static extern IntPtr OpenThemeData(IntPtr hWnd, String classList);
[DllImport("uxtheme.dll", ExactSpelling=true)]
public extern static Int32 DrawThemeBackground(IntPtr hTheme, IntPtr hdc, int iPartId,
int iStateId, ref RECT pRect, IntPtr pClipRect);
[DllImport("uxtheme.dll", ExactSpelling=true)]
public extern static Int32 CloseThemeData(IntPtr hTheme);
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case WM_NCPAINT:
WmPaint(ref m);
break;
default:
base.WndProc (ref m);
break;
}
}
public void WmPaint(ref Message m) {
try
{
base.WndProc(ref m);
if (!IsThemeActive()) {
return;
}
int partId = (int)BUTTONPARTS.BP_PUSHBUTTON;
int stateId = (int)PUSHBUTTONSTATES.PBS_NORMAL;
RECT windowRect;
GetWindowRect(this.Handle, out windowRect);
IntPtr hDC = GetWindowDC(this.Handle);
IntPtr hTheme = OpenThemeData(this.Handle, "Button");
DrawThemeBackground(hTheme, hDC, partId, stateId, ref windowRect, IntPtr.Zero);
CloseThemeData(hTheme);
ReleaseDC(this.Handle, hDC);
m.Result = IntPtr.Zero;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
|
|
|
|
 |
|
|
 |
|
 |
Bernhard, i am very glad to see your reply and thank you very much for your help. I have visited the link you gave me before and didn't pay attention to it. You remind me and I have solved my problem with the help of FreeStyle. I will record my experience in CodeProject later. Maybe other guys need it.
By the way, wish you a happy new year.
Yours sincerely,
Gaojb
|
|
|
|
 |
|
 |
Great work on the control BUT (isn't there always a but...) I'm trying to use it in a custom FlowLayoutPanel and it seems have slightly broken metrics when compared to either a normal RichTextBox or a TextBox.
This custom FlowLayoutPanel is basically a property display that uses the GetPreferredSize mechanism to layout property names and property values in a grid. The important point here is that the controls are 'wrangled' so that they fit in a given width and are only allowed to expand vertically.
This all works brilliantly with TextBoxes and RichTextBoxes - however when I try to use your RichTextBoxEx the metrics are slightly out leading to a situation where a single line of text in a multline RichTextBoxEx is sized too small and I get a squashed vertical scrollbar.
I can work around this issue by either removing the contentRect.Inflate(-1, -1) call in WmNccalcsize() function or by overriding the GetPreferredSize() call and inflating the returned size.
However, from some of the instrumentation I have in my code I have determined that both the TextBox and RichTextBox return a height of 20 for a single line of text when enabled for editing, were as you control returns a 16 - which is too small.
I'm wondering if you're missing the border widths in one of your metric calculations, if you could have a look at this and make your control match the RichTextBox's behaviour that would be great.
If you'd like an example app I'm sure I could knock one up.
|
|
|
|
 |
|
 |
Hi,
thanks for this information. I would be great if you could send an example app.
At all, if you are using .NET 2.0 or higher you should use the following solution posted by "mjelten"...
----------------------------------------------------------------------------------
Put the RichTextBox in a panel, dock the RichTextBox in a panel, set BorderStyle to none.
set
panel.BackColor = System.Windows.Forms.VisualStyles.VisualStyleInformation.TextControlBorder;
panel.Padding = new Padding(1);
|
|
|
|
 |
|
 |
Put the RichTextBox in a panel, dock the RichTextBox in a panel, set BorderStyle to none.
set
panel.BackColor = System.Windows.Forms.VisualStyles.VisualStyleInformation.TextControlBorder;
panel.Padding = new Padding(1);
-- modified at 4:55 Friday 20th July, 2007
|
|
|
|
 |
|
 |
I think your solution is not "uglier".
Your advantage is that you have no need to pinvoke. By sure you could say "it´s bug free".
But my article is also a sample for message handling (WM_PAINT), using uxtheme.dll and calling functions of GDI.
|
|
|
|
 |
|
 |
Genious solution, thanks!
I will base my solution on your message
|
|
|
|
 |
|
 |
Excellent job dude. *bows*.
Works perfect on Windows NT 6 (Vista Store Retail)
|
|
|
|
 |
|
 |
Here is a simplified VB.Net 2.0 version
Imports System.Runtime.InteropServices
Imports System.Windows.Forms.VisualStyles
Public Class RichTextBoxEx
Inherits RichTextBox
Public Event NonClientPaint As EventHandler(Of PaintEventArgs)
Private m_Render As VisualStyles.VisualStyleRenderer
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case WM_NCPAINT
WmNcPaint(m)
Case Else
MyBase.WndProc(m)
End Select
End Sub
Private Sub WmNcPaint(ByRef m As Message)
MyBase.WndProc(m)
Dim hDC As IntPtr = GetWindowDC(m.HWnd)
Dim b As New Rectangle(0, 0, Me.Width, Me.Height)
Using a As New PaintEventArgs(Graphics.FromHdc(hDC), b)
Me.OnNonClientPaint(a)
End Using
End Sub
Protected Overridable Sub OnNonClientPaint(ByVal e As PaintEventArgs)
If Application.RenderWithVisualStyles Then
If m_Render Is Nothing Then
m_Render = New VisualStyleRenderer(VisualStyleElement.TextBox.TextEdit.Normal)
End If
m_Render.DrawBackground(e.Graphics, e.ClipRectangle)
End If
RaiseEvent NonClientPaint(Me, e)
End Sub
Private Const WM_NCPAINT As Integer = &H85
<DllImport("user32.dll")> _
Private Shared Function GetWindowDC(ByVal hWnd As IntPtr) As IntPtr
End Function
End Class
|
|
|
|
 |
|
 |
1. You must pinvoke ReleaseDC function after GetWindowDC function.
2. You do not modify the client area during the WM_NCCALC message. So with a lot of Windows Themes this version will not work correctly.
-- modified at 9:11 Monday 12th February, 2007
|
|
|
|
 |
|
 |
1. When the PaintEventArgs gets disposed it calls Graphics.Dispose which in turn calls Graphics.ReleaseHdc which pinvokes ReleaseDC
2. I cheat and set the border style to Windows.Forms.BorderStyle.Fixed3D which makes the client area big enough for the standard XP themes
3. My code also sufferes from re-draw problems due to not calling ExcludeClipRect
-- modified at 6:42 Tuesday 24th July, 2007
|
|
|
|
 |
|
 |
.NET 2.0 provides some useful things:
Private IsAppThemed As Boolean = VisualStyleInformation.IsEnabledByUser AndAlso Application.RenderWithVisualStyles
ControlPaint.DrawBorder(CreateGraphics, ClientRectangle, VisualStyleInformation.TextControlBorder, ButtonBorderStyle.Solid)
|
|
|
|
 |
|
 |
¿Why when I change Multiline to false (in the designer), the border don't see?
Thank you very much (the component is very useful for me).
|
|
|
|
 |
|
 |
Thank you for reporting this. I was able to reproduce it.
When a comment out the first two lines of WmncPaint method everything is working fine. I will update the article.
|
|
|
|
 |
|
 |
Excuse me, but I am not able to put the comment in the correct line.
The first two lines of WmNcpaint method are:
base.WndProc(ref m);
if(!this.RenderWithVisualStyles())
{
return;
}
where put the comment?
If I put in the first or in the second or in both, don't work fine.
(I am blind today, excuse me).
|
|
|
|
 |
|
 |
Ohh i´m so sorry, wrong method. I mean WmNccalcsize look at this...
///
/// Calculates the size of the window frame and client area of the RichTextBox
///
void WmNccalcsize(ref Message m)
{
// // when WParam is zero there is nothing to do.
// if(m.WParam == IntPtr.Zero)
// return;
|
|
|
|
 |
|
 |
I'm crazy!!!. I'm sorry, but with the comment in WmNccalcsize the border of RichTextBoxEx don't draw it when I set Multiline to false.
|
|
|
|
 |
|
 |
Bug is fixed now. Article is updated.
Thank you for reporting this.
|
|
|
|
 |
|
 |
I've created a UserControl which contains your RichTextBoxEx. This bit works great - thanks. I can't understand why MS didn't do this themselves: your version looks so much better.
My RichTextEditor also contains a ToolStrip and a StatusBar. To give the thing a professional finish, I wanted to give the whole UserControl the same Themed border. I created a UserControlEx, inherting from UserControl and added your code (minus three lines:
if (this.ReadOnly)
 stateId = NativeMethods.ETS_READONLY;
else
because ReadOnly doesn't make sense in this context). Now, it works in the designer for the RichTextEditor (which derives from UserControlEx). And it works in the designer for a Form which uses the RichTextEditor. But when I debug the program, there's no border. Any idea why?
For info, I also tried adding your code to a PanelEx and saw the same: it works in the designer but not when the code is run.
|
|
|
|
 |
|
 |
When you have a .NET UserControl do not render the border in the WM_NCPAINT message. Overriding the OnPaint or the OnPaintBackground method is "The .NET Way" for .NET Controls. Do you use .NET 1.1 or .NET 2.0? If you use the new framework, then have a look into the System.Windows.Forms.VisualStyles namespace. So you have no need to pinvoke and also you will get your job done in only two lines of code.
Hope this helps.
|
|
|
|
 |
|
 |
Hi, Great article.
I've tried doing this using the System.Windows.Forms.visualStyles namespace like you suggest for .NET 2.0. I do the drawing in the OnPaint method, but then the border is just drawn inside the client rectangle. Is it possible to draw the border outside the client rectangle like you've done in your article, but using "the .net way" of doing things?
Simon
|
|
|
|
 |