I have an WinForms app built with vb .net to which I've added a WPF user control. The WPF user control consists of only a progressbar. I can drag the WPF control from the toolbax and add it to the Main Form in WinForms vb .net. But I don't know how to let my WinForms app dynamically set the value for the WPF progress bar. Anyway I can dynamically set the WPF control progress bar's value from my WinForms app?
Note: The reason Im using WPF progress bar instead of using WinForms progress bar is so that I can have blue colored progress bar in my WinForms app.
Since it may not be as straightforward as it may look like on paper, here's a custom ProgressBar that allows to set the Color of the bar and also change its style on the fly.
The ProgressBarStyle property sets the current style: the BarStyle.Standard style uses the ProgressBarRenderer.DrawHorizontalBar() method to draw its background, while the BarStyle.Flat selects the Parent.BackColor as background color and uses a fixed white border to draw the ProgressBar bounds and SystemColors.Highlight as the bar color. Of course it can be changed to support any other color and the Standard style.
The StringFormat class is used to center the text of the ProgressBar (in Standard style only. This can also be easily changed), setting both Horizontal and Vertical alignment to StringAlignment.Center.
The text color changes automatically to adapt to the Brightness of the ProgressBar color (it's just a simple adjustment, the HSL value on its own cannot always determine what text color better fits a background color).
Some magic values are just offsets used to adapt the drawing rectangles to the shapes designed by ProgressBarRenderer
This is how it works:
VB.Net version of the Custom Control:
Imports System.ComponentModel
Imports System.Drawing.Drawing2D
Imports System.Drawing
Imports System.Windows.Forms
<DesignerCategory("Code")>
Public Class ProgressBarCustomColor
Inherits ProgressBar
Private m_ParentBackColor As Color = SystemColors.Window
Private m_ProgressBarStyle As BarStyle = BarStyle.Standard
Private m_FlatBorderColor As Color = Color.White
Public Sub New()
SetStyle(ControlStyles.AllPaintingInWmPaint Or
ControlStyles.UserPaint Or
ControlStyles.OptimizedDoubleBuffer, True)
End Sub
Public Enum BarStyle
Standard
Flat
End Enum
Public Property ProgressBarStyle As BarStyle
Get
Return m_ProgressBarStyle
End Get
Set
m_ProgressBarStyle = Value
If DesignMode Then Me.Parent?.Refresh()
End Set
End Property
Public Property FlatBorderColor As Color
Get
Return m_FlatBorderColor
End Get
Set
m_FlatBorderColor = Value
If DesignMode Then Me.Parent?.Refresh()
End Set
End Property
Protected Overrides Sub OnParentChanged(e As EventArgs)
MyBase.OnParentChanged(e)
m_ParentBackColor = If(Me.Parent IsNot Nothing, Me.Parent.BackColor, SystemColors.Window)
End Sub
Protected Overrides Sub OnPaintBackground(e As PaintEventArgs)
MyBase.OnPaintBackground(e)
Dim rect = Me.ClientRectangle
If m_ProgressBarStyle = BarStyle.Standard Then
ProgressBarRenderer.DrawHorizontalBar(e.Graphics, rect)
Dim baseColor = Color.FromArgb(240, Me.BackColor)
Dim highColor = Color.FromArgb(160, baseColor)
Using baseBrush = New SolidBrush(baseColor),
highBrush = New SolidBrush(highColor)
e.Graphics.FillRectangle(highBrush, 1, 2, rect.Width, 9)
e.Graphics.FillRectangle(baseBrush, 1, 7, rect.Width, rect.Height - 1)
End Using
Else
Using pen = New Pen(m_FlatBorderColor, 2),
baseBrush = New SolidBrush(m_ParentBackColor)
e.Graphics.FillRectangle(baseBrush, 0, 0, rect.Width - 1, rect.Height - 1)
e.Graphics.DrawRectangle(pen, 1, 1, Me.ClientSize.Width - 2, Me.ClientSize.Height - 2)
End Using
End If
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
Dim rect = New RectangleF(PointF.Empty, Me.ClientSize)
rect.Width = CType(rect.Width * (CType(Me.Value, Single) / Me.Maximum), Integer)
rect.Size = New SizeF(rect.Width - 2, rect.Height)
Dim hsl As Single = Me.ForeColor.GetBrightness()
If m_ProgressBarStyle = BarStyle.Standard Then DrawStandardBar(e.Graphics, rect, hsl)
If m_ProgressBarStyle = BarStyle.Flat Then DrawFlatBar(e.Graphics, rect, hsl)
End Sub
Private Sub DrawStandardBar(g As Graphics, rect As RectangleF, hsl As Single)
g.SmoothingMode = SmoothingMode.AntiAlias
g.CompositingQuality = CompositingQuality.HighQuality
Dim baseColor = Color.FromArgb(240, Me.ForeColor)
Dim highColor = Color.FromArgb(160, baseColor)
Using baseBrush = New SolidBrush(baseColor),
highBrush = New SolidBrush(highColor),
sf = New StringFormat(StringFormatFlags.MeasureTrailingSpaces)
sf.LineAlignment = StringAlignment.Center
sf.Alignment = StringAlignment.Center
g.FillRectangle(highBrush, 1, 2, rect.Width, 9)
g.FillRectangle(baseBrush, 1, 7, rect.Width, rect.Height - 1)
g.DrawString($"{Me.Value} %", Me.Parent.Font,
If(hsl > 0.49F, Brushes.Black, Brushes.White), Me.ClientRectangle, sf)
End Using
End Sub
Private Sub DrawFlatBar(g As Graphics, rect As RectangleF, hsl As Single)
Using baseBrush = New SolidBrush(SystemColors.Highlight)
g.FillRectangle(baseBrush, 2, 2, rect.Width - 2, rect.Height - 4)
End Using
End Sub
End Class
C# version:
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class ProgressBarCustomColor : ProgressBar
{
private Color m_ParentBackColor = SystemColors.Window;
private Color m_FlatBorderColor = Color.White;
private BarStyle m_ProgressBarStyle = BarStyle.Standard;
public ProgressBarCustomColor()
{
this.SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.OptimizedDoubleBuffer, true);
}
public enum BarStyle { Standard, Flat }
public BarStyle ProgressBarStyle {
get => m_ProgressBarStyle;
set {
m_ProgressBarStyle = value;
if (DesignMode) this.Parent?.Refresh();
}
}
public Color FlatBorderColor {
get => m_FlatBorderColor;
set {
m_FlatBorderColor = value;
if (DesignMode) this.Parent?.Refresh();
}
}
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
m_ParentBackColor = this.Parent?.BackColor ?? SystemColors.Window;
}
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
var rect = this.ClientRectangle;
if (m_ProgressBarStyle == BarStyle.Standard) {
ProgressBarRenderer.DrawHorizontalBar(e.Graphics, rect);
var baseColor = Color.FromArgb(240, this.BackColor);
var highColor = Color.FromArgb(160, baseColor);
using (var baseBrush = new SolidBrush(baseColor))
using (var highBrush = new SolidBrush(highColor)) {
e.Graphics.FillRectangle(highBrush, 1, 2, rect.Width, 9);
e.Graphics.FillRectangle(baseBrush, 1, 7, rect.Width, rect.Height - 1);
}
}
else {
using (var pen = new Pen(m_FlatBorderColor, 2))
using (var baseBrush = new SolidBrush(m_ParentBackColor)) {
e.Graphics.FillRectangle(baseBrush, 0, 0, rect.Width - 1, rect.Height - 1);
e.Graphics.DrawRectangle(pen, 1, 1, this.ClientSize.Width - 2, this.ClientSize.Height - 2);
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
var rect = new RectangleF(PointF.Empty, this.ClientSize);
rect.Width = (int)(rect.Width * ((float)this.Value / this.Maximum));
rect.Size = new SizeF(rect.Width - 2, rect.Height);
float hsl = this.ForeColor.GetBrightness();
if (m_ProgressBarStyle == BarStyle.Standard) DrawStandardBar(e.Graphics, rect, hsl);
if (m_ProgressBarStyle == BarStyle.Flat) DrawFlatBar(e.Graphics, rect, hsl);
}
private void DrawStandardBar(Graphics g, RectangleF rect, float hsl)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.CompositingQuality = CompositingQuality.HighQuality;
var baseColor = Color.FromArgb(240, this.ForeColor);
var highColor = Color.FromArgb(160, baseColor);
using (var baseBrush = new SolidBrush(baseColor))
using (var highBrush = new SolidBrush(highColor))
using (var sf = new StringFormat(StringFormatFlags.MeasureTrailingSpaces)) {
sf.LineAlignment = StringAlignment.Center;
sf.Alignment = StringAlignment.Center;
g.FillRectangle(highBrush, 1, 2, rect.Width, 9);
g.FillRectangle(baseBrush, 1, 7, rect.Width, rect.Height - 1);
g.DrawString($"{this.Value} %", this.Parent.Font,
hsl > .49f ? Brushes.Black : Brushes.White, this.ClientRectangle, sf);
}
}
private void DrawFlatBar(Graphics g, RectangleF rect, float hsl)
{
using (var baseBrush = new SolidBrush(SystemColors.Highlight)) {
g.FillRectangle(baseBrush, 2, 2, rect.Width - 2, rect.Height - 4);
}
}
}
Related
I'm drawing the following Shape in a Canvas.
I would like to highlight it when it's selected by changing its color (the easy part) and drawing an small halo around it:
This is how I did using SASS: http://codepen.io/aaromnido/pen/zKvAwd/
How coud I draw in WPF? Remember that I'm drawing using the Shape's OnRender method.
Set some defaults in constructor.
One of these defaults is Shape.Effect, as it will be animated on MouseEnter event.
Construct VisualStates for Normal , and MouseEnter scenarios.
Change the VisualState of the element using VisualStateManager.GoToElementState() in MouseEnter and MouseLeave event handlers.
You can expose various properties using DPs for customization.
NewShape.cs
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Media.Effects;
namespace WpfStackOverflow.NewShape
{
public class CNewShape : Shape
{
public CNewShape()
{
// setting the defaults
this.Width = 40;
this.Height = 40;
this.Stroke = new SolidColorBrush() { Color = Colors.Red };
this.StrokeThickness = 5;
this.Effect = new DropShadowEffect() {
Color = Colors.Transparent,
BlurRadius = 1,
Direction = -150,
ShadowDepth = 1
};
// constructing the VisualStates
_constructVisualStates();
// event handlers
this.MouseEnter += CNewShape_MouseEnter;
this.MouseLeave += CNewShape_MouseLeave;
}
#region EventHandlers
void CNewShape_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToElementState(this, "VSNormal", false);
}
void CNewShape_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToElementState(this, "VSMouseEnter", false);
}
#endregion
#region Overrides
// This needs to be implemented as it is abstract in base class
GeometryGroup geo = new GeometryGroup();
protected override Geometry DefiningGeometry
{
get { return geo; }
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Pen pen = new Pen(this.Stroke, StrokeThickness);
drawingContext.DrawEllipse(Brushes.Transparent, pen, new Point(Width/2, Height/2), 40, 40);
drawingContext.DrawEllipse(Stroke, null, new Point(Width / 2, Height / 2), 30, 30);
base.OnRender(drawingContext);
}
#endregion
#region Helpers
private void _constructVisualStates()
{
VisualStateGroup vsg1 = new VisualStateGroup();
#region VSNormal (Normal Visual State)
VisualState stateVSNormal = new VisualState() { Name = "VSNormal" };
Storyboard sbVSNormal = new Storyboard();
ObjectAnimationUsingKeyFrames oa = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTargetProperty(oa, new PropertyPath("Effect"));
DiscreteObjectKeyFrame dokf = new DiscreteObjectKeyFrame(null);
oa.KeyFrames.Add(dokf);
sbVSNormal.Children.Add(oa);
stateVSNormal.Storyboard = sbVSNormal;
vsg1.States.Add(stateVSNormal);
#endregion
#region VSMouseEnter (MouseEnter Visual State)
VisualState stateVSMouseEnter = new VisualState() { Name = "VSMouseEnter" };
Storyboard sbVSMouseEnter = new Storyboard();
ColorAnimation caStrokeColor = new ColorAnimation();
caStrokeColor.To = (Color)ColorConverter.ConvertFromString("#FF24BCDE");
Storyboard.SetTargetProperty(caStrokeColor, new PropertyPath("(Shape.Stroke).(SolidColorBrush.Color)"));
sbVSMouseEnter.Children.Add(caStrokeColor);
ColorAnimation caEffectColor = new ColorAnimation();
caEffectColor.To = (Color)ColorConverter.ConvertFromString("#FFA4E1F3");
Storyboard.SetTargetProperty(caEffectColor, new PropertyPath("(Shape.Effect).(Color)"));
sbVSMouseEnter.Children.Add(caEffectColor);
DoubleAnimation daBlurRadius = new DoubleAnimation();
daBlurRadius.To = 10;
Storyboard.SetTargetProperty(daBlurRadius, new PropertyPath("(Shape.Effect).(BlurRadius)"));
sbVSMouseEnter.Children.Add(daBlurRadius);
DoubleAnimation daDirection = new DoubleAnimation();
daDirection.To = -190;
Storyboard.SetTargetProperty(daDirection, new PropertyPath("(Shape.Effect).(Direction)"));
sbVSMouseEnter.Children.Add(daDirection);
stateVSMouseEnter.Storyboard = sbVSMouseEnter;
vsg1.States.Add(stateVSMouseEnter);
#endregion
VisualStateManager.GetVisualStateGroups(this).Add(vsg1);
}
#endregion
}
}
Usage
<local:CNewShape Canvas.Left="70" Canvas.Top="52" Stroke="#FF374095" StrokeThickness="10" Width="100" Height="100" />
Output
Quality of the image is bad. On screen actual output looks good.
Whatever your trigger is that your control enters the Highlighted state, in that trigger just set the Effect property. For my test the "trigger" is a property:
public static readonly DependencyProperty ShowShadowProperty =
DependencyProperty.Register ("ShowShadow", typeof (bool), typeof (TestShape), new PropertyMetadata (false, ShowShadowChanged));
public bool ShowShadow
{
get { return (bool)GetValue (ShowShadowProperty); }
set { SetValue (ShowShadowProperty, value); }
}
private static void ShowShadowChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TestShape)d).OnShowShadow ();
}
private void OnShowShadow ()
{
if (ShowShadow)
{
Effect = new DropShadowEffect { Direction = 0, ShadowDepth = 20, BlurRadius = 33, Opacity = 1, Color = Colors.Black};
}
else
{
Effect = null;
}
}
Which means you don't need to do anything in OnRender.
Hey all i am trying to add a label to my grid using the following code:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim dynamicLabel As New Label()
Dim g As New Grid
dynamicLabel.Name = "NewLabel"
dynamicLabel.Content = "TEST"
dynamicLabel.Width = 240
dynamicLabel.Height = 30
dynamicLabel.Margin = New Thickness(0, 21, 0, 0)
dynamicLabel.Foreground = New SolidColorBrush(Colors.White)
dynamicLabel.Background = New SolidColorBrush(Colors.Black)
Grid.SetRow(dynamicLabel, 0)
Grid.SetColumn(dynamicLabel, 6)
g.Children.Add(dynamicLabel)
End Sub
However, i never see anything on the grid after i push the button... what am i missing?
I corrected this by naming the other grid. I never had the grid named and therefore thats why the .Children.Add never would add to the grid. I tried mainGrid.Children.Add (mainGrid is the name of my parent grid) and it would always throw an error because it was the grid inside that it was needing for this part.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) //Handles Button1.Click
Dim dynamicLabel As new Label();
dynamicLabel.Name = "NewLabel";
dynamicLabel.Content = "TEST";
dynamicLabel.Width = 240;
dynamicLabel.Height = 30;
dynamicLabel.Margin = new Thickness(0, 21, 0, 0);
dynamicLabel.Foreground = new SolidColorBrush(Colors.White);
dynamicLabel.Background = new SolidColorBrush(Colors.Black);
Grid.SetRow(dynamicLabel, 0);
Grid.SetColumn(dynamicLabel, 0);
childGrid.Children.Add(dynamicLabel); //'<-changed to grid name in XAML properties
End Sub
If you need to add a button or a Label (just change Button to Label) to a Grid, you can use this:
internal void FillbtnSubCat(Grid grid)
{
var myDefinition = new ColumnDefinition();
var myButton = new Button();
Grid.SetColumn(myButton, count);
myButton.Margin = new Thickness(5, 10, 5, 25);
myButton.MinWidth = 30;
myButton.Content = count;
myButton.Click+= new RoutedEventHandler(Click1);
myDefinition.Width = new GridLength(68);
grid.ColumnDefinitions.Add(myDefinition);
grid.Children.Add(myButton);
count++;
}
void Click1(object sender, RoutedEventArgs e)
{
var myButton = sender as Button;
MessageBox.Show(myButton.GetHashCode().ToString());
}
and XAML
<ListView Grid.Column="0" Margin="10,10,5,5" Grid.Row="1" Grid.ColumnSpan="2">
<Grid Name="Grid1" Height="100" Width="auto">
</Grid>
</ListView>
gride is the name of my gride in dynamicForm.xaml window.
it creates button and add to gride, then in button click evnt it creates a label by clicking on a the button.
public partial class DynamicForm : Window
{
Label lb = new Label();
public DynamicForm()
{
InitializeComponent();
Button dynamicButton = new Button();
dynamicButton.Content = "Click me";
Grid.SetRow(dynamicButton, 0);
Grid.SetColumn(dynamicButton, 0);
gride.Children.Add(dynamicButton);
dynamicButton.Click += button1_Click;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Label dynamicLabel = new Label();
dynamicLabel.Name = "NewLabel";
dynamicLabel.Content = "TEST";
dynamicLabel.Width = 240;
dynamicLabel.Height = 30;
dynamicLabel.Margin = new Thickness(0, 21, 0, 0);
dynamicLabel.Foreground = new SolidColorBrush(Colors.White);
dynamicLabel.Background = new SolidColorBrush(Colors.Black);
Grid.SetRow(dynamicLabel, 1);
Grid.SetColumn(dynamicLabel, 0);
gride.Children.Add(dynamicLabel);
}
}
In the shown code you never add the new grid to any window/control in a window.
I am using a custom tooltip class in my WinForm application. It works great. But i would like to show it as a Balloon. Here is my code-
class CustomToolTip : ToolTip
{
public CustomToolTip()
{
this.OwnerDraw = true;
this.Popup += new PopupEventHandler(this.OnPopup);
this.Draw += new DrawToolTipEventHandler(this.OnDraw);
}
private void OnPopup(object sender, PopupEventArgs e) // use this event to set the size of the tool tip
{
e.ToolTipSize = new Size(200, 100);
}
private void OnDraw(object sender, DrawToolTipEventArgs e) // use this event to customise the tool tip
{
Graphics g = e.Graphics;
LinearGradientBrush b = new LinearGradientBrush(e.Bounds,
Color.GreenYellow, Color.MintCream, 45f);
g.FillRectangle(b, e.Bounds);
g.DrawRectangle(new Pen(Brushes.Red, 1), new Rectangle(e.Bounds.X, e.Bounds.Y,
e.Bounds.Width - 1, e.Bounds.Height - 1));
g.DrawString(e.ToolTipText, new Font(e.Font, FontStyle.Bold), Brushes.Silver,
new PointF(e.Bounds.X + 6, e.Bounds.Y + 6)); // shadow layer
g.DrawString(e.ToolTipText, new Font(e.Font, FontStyle.Bold), Brushes.Black,
new PointF(e.Bounds.X + 5, e.Bounds.Y + 5)); // top layer
b.Dispose();
}
}
Any suggestion?
Thanking you in anticipation.
You could just draw an ellipse then add a piece to point to the target.
But why not use the default tooltip control.
It has a IsBalloon flag that when set to true looks like this:
Can this be done with relative ease?
I was able to do the following in 15 minutes, so yes. The main idea is to handle the DrawItem event.
Following is my take on the problem (you can see another example, drawing icons in the items here).
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.comboBox1.DrawMode = DrawMode.OwnerDrawVariable;
this.comboBox1.DrawItem += new DrawItemEventHandler(comboBox1_DrawItem);
this.comboBox1.Items.Add("Some text that needs to be take up two lines...");
this.comboBox1.ItemHeight = 30;
}
IEnumerable<string> WrapString(string str, Graphics g, Font font,
int allowedWidth)
{
string[] arr = str.Split(' ');
StringBuilder current = new StringBuilder();
foreach (string token in arr)
{
// TODO: You'll have to fix this, might break in marginal cases
int width =
(int)g.MeasureString(current.ToString() + " " + token, font).Width;
if (width > allowedWidth)
{
yield return current.ToString();
current.Clear();
}
current.Append(token + " ");
}
yield return current.ToString();
}
void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{
Brush backgroundBrush, forgroundBrush;
if (e.State == (DrawItemState.Selected |
DrawItemState.NoAccelerator | DrawItemState.NoFocusRect) ||
e.State == DrawItemState.Selected)
{
forgroundBrush = Brushes.Black;
backgroundBrush = Brushes.White;
}
else
{
forgroundBrush = Brushes.White;
backgroundBrush = Brushes.Black;
}
// some way to wrap the string (on a space)
string str = (string)comboBox1.Items[e.Index];
Rectangle rc =
new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
e.Graphics.FillRectangle(forgroundBrush, rc);
int stringHeight =
(int)e.Graphics.MeasureString(str, comboBox1.Font).Height;
int lineNo = 0;
foreach (string line in
WrapString(str, e.Graphics, comboBox1.Font, e.Bounds.Width))
{
e.Graphics.DrawString(line, comboBox1.Font, backgroundBrush,
new PointF(0, lineNo * stringHeight + 5));
lineNo++;
}
}
}
Usage: Create a regular form and drop one combobox on it.
(Note that this is of course only a naïve proof of concept - there's obviously room for improvement. Also it's just assuming that there will only be two lines rather than one. But it shows that this is possible.)
I found this class made by Tim Mackey which worked out really well in my project (Tim's Blog Entry):
C# Version:
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections.Generic;
namespace HortLaptopApp
{
class ComboBoxWrap : ComboBox
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
public const int SWP_NOZORDER = 0x0004;
public const int SWP_NOACTIVATE = 0x0010;
public const int SWP_FRAMECHANGED = 0x0020;
public const int SWP_NOOWNERZORDER = 0x0200;
public const int WM_CTLCOLORLISTBOX = 0x0134;
private int _hwndDropDown = 0;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
if (_hwndDropDown == 0)
{
_hwndDropDown = m.LParam.ToInt32();
RECT r;
GetWindowRect((IntPtr)_hwndDropDown, out r);
//int newHeight = 0;
// for(int i=0; i<Items.Count && i < MaxDropDownItems; i++)
// newHeight += this.GetItemHeight(i);
int total = 0;
for (int i = 0; i < this.Items.Count; i++)
total += this.GetItemHeight(i);
this.DropDownHeight = total + SystemInformation.BorderSize.Height * (this.Items.Count + 2);
SetWindowPos((IntPtr)_hwndDropDown, IntPtr.Zero,
r.Left,
r.Top,
DropDownWidth,
DropDownHeight,
SWP_FRAMECHANGED |
SWP_NOACTIVATE |
SWP_NOZORDER |
SWP_NOOWNERZORDER);
}
}
base.WndProc(ref m);
}
protected override void OnDropDownClosed(EventArgs e)
{
_hwndDropDown = 0;
base.OnDropDownClosed(e);
}
public ComboBoxWrap() : base()
{
// add event handlers
this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
this.DrawItem += new DrawItemEventHandler(ComboBoxWrap_DrawItem);
this.MeasureItem += new MeasureItemEventHandler(ComboBoxWrap_MeasureItem);
}
void ComboBoxWrap_MeasureItem(object sender, MeasureItemEventArgs e)
{
// set the height of the item, using MeasureString with the font and control width
ComboBoxWrap ddl = (ComboBoxWrap)sender;
string text = ddl.Items[e.Index].ToString();
SizeF size = e.Graphics.MeasureString(text, this.Font, ddl.DropDownWidth);
e.ItemHeight = (int)Math.Ceiling(size.Height) + 1; // plus one for the border
e.ItemWidth = ddl.DropDownWidth;
System.Diagnostics.Trace.WriteLine(String.Format("Height {0}, Text {1}", e.ItemHeight, text));
}
void ComboBoxWrap_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index < 0)
return;
// draw a lighter blue selected BG colour, the dark blue default has poor contrast with black text on a dark blue background
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
e.Graphics.FillRectangle(Brushes.PowderBlue, e.Bounds);
else
e.Graphics.FillRectangle(Brushes.White, e.Bounds);
// get the text of the item
ComboBoxWrap ddl = (ComboBoxWrap)sender;
string text = ddl.Items[e.Index].ToString();
// don't dispose the brush afterwards
Brush b = Brushes.Black;
e.Graphics.DrawString(text, this.Font, b, e.Bounds, StringFormat.GenericDefault);
// draw a light grey border line to separate the items
Pen p = new Pen(Brushes.Gainsboro, 1);
e.Graphics.DrawLine(p, new Point(e.Bounds.Left, e.Bounds.Bottom-1), new Point(e.Bounds.Right, e.Bounds.Bottom-1));
p.Dispose();
e.DrawFocusRectangle();
}
}
}
VB Version:
Imports System.Drawing
Imports System.Linq
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports System.Collections.Generic
Namespace HortLaptopApp
Class ComboBoxWrap
Inherits ComboBox
<DllImport("user32.dll")> _
Public Shared Function GetWindowRect(hwnd As IntPtr, ByRef lpRect As RECT) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("user32.dll", SetLastError := True)> _
Private Shared Function SetWindowPos(hWnd As IntPtr, hWndInsertAfter As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, _
uFlags As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<StructLayout(LayoutKind.Sequential)> _
Public Structure RECT
Public Left As Integer
' x position of upper-left corner
Public Top As Integer
' y position of upper-left corner
Public Right As Integer
' x position of lower-right corner
Public Bottom As Integer
' y position of lower-right corner
End Structure
Public Const SWP_NOZORDER As Integer = &H4
Public Const SWP_NOACTIVATE As Integer = &H10
Public Const SWP_FRAMECHANGED As Integer = &H20
Public Const SWP_NOOWNERZORDER As Integer = &H200
Public Const WM_CTLCOLORLISTBOX As Integer = &H134
Private _hwndDropDown As Integer = 0
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WM_CTLCOLORLISTBOX Then
If _hwndDropDown = 0 Then
_hwndDropDown = m.LParam.ToInt32()
Dim r As RECT
GetWindowRect(DirectCast(_hwndDropDown, IntPtr), r)
'int newHeight = 0;
' for(int i=0; i<Items.Count && i < MaxDropDownItems; i++)
' newHeight += this.GetItemHeight(i);
Dim total As Integer = 0
For i As Integer = 0 To Me.Items.Count - 1
total += Me.GetItemHeight(i)
Next
Me.DropDownHeight = total + SystemInformation.BorderSize.Height * (Me.Items.Count + 2)
SetWindowPos(DirectCast(_hwndDropDown, IntPtr), IntPtr.Zero, r.Left, r.Top, DropDownWidth, DropDownHeight, _
SWP_FRAMECHANGED Or SWP_NOACTIVATE Or SWP_NOZORDER Or SWP_NOOWNERZORDER)
End If
End If
MyBase.WndProc(m)
End Sub
Protected Overrides Sub OnDropDownClosed(e As EventArgs)
_hwndDropDown = 0
MyBase.OnDropDownClosed(e)
End Sub
Public Sub New()
MyBase.New()
' add event handlers
Me.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable
Me.DrawItem += New DrawItemEventHandler(AddressOf ComboBoxWrap_DrawItem)
Me.MeasureItem += New MeasureItemEventHandler(AddressOf ComboBoxWrap_MeasureItem)
End Sub
Private Sub ComboBoxWrap_MeasureItem(sender As Object, e As MeasureItemEventArgs)
' set the height of the item, using MeasureString with the font and control width
Dim ddl As ComboBoxWrap = DirectCast(sender, ComboBoxWrap)
Dim text As String = ddl.Items(e.Index).ToString()
Dim size As SizeF = e.Graphics.MeasureString(text, Me.Font, ddl.DropDownWidth)
e.ItemHeight = CInt(Math.Ceiling(size.Height)) + 1
' plus one for the border
e.ItemWidth = ddl.DropDownWidth
System.Diagnostics.Trace.WriteLine([String].Format("Height {0}, Text {1}", e.ItemHeight, text))
End Sub
Private Sub ComboBoxWrap_DrawItem(sender As Object, e As DrawItemEventArgs)
If e.Index < 0 Then
Return
End If
' draw a lighter blue selected BG colour, the dark blue default has poor contrast with black text on a dark blue background
If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
e.Graphics.FillRectangle(Brushes.PowderBlue, e.Bounds)
Else
e.Graphics.FillRectangle(Brushes.White, e.Bounds)
End If
' get the text of the item
Dim ddl As ComboBoxWrap = DirectCast(sender, ComboBoxWrap)
Dim text As String = ddl.Items(e.Index).ToString()
' don't dispose the brush afterwards
Dim b As Brush = Brushes.Black
e.Graphics.DrawString(text, Me.Font, b, e.Bounds, StringFormat.GenericDefault)
' draw a light grey border line to separate the items
Dim p As New Pen(Brushes.Gainsboro, 1)
e.Graphics.DrawLine(p, New Point(e.Bounds.Left, e.Bounds.Bottom - 1), New Point(e.Bounds.Right, e.Bounds.Bottom - 1))
p.Dispose()
e.DrawFocusRectangle()
End Sub
End Class
End Namespace
This question is kind of old, but I found that the DrawString with a RectangleF parameter not only clips text to that rectangle, but also wraps it.
Sample code:
StringFormat sf = StringFormat.GenericTypographic;
sf.Trimming = StringTrimming.EllipsisCharacter;
g.DrawString(text, font, foregroundBrush, e.Bounds, sf);
See also MS Docs: How to: Display Side-Aligned Tabs with TabControl
I enhanced Steinar's answer, which gave me a wonderful combobox that could hold variable multilined texts, including a tooltip for each item in the list.
Here is what to do:
Setup DrawItem, MeasureItem events for the combobox as follows. Also use the Wrapstring() method to yield each chunk of the multiline to compute line count and height of each line:
// Goes in Form load / constructor
cmbMulti.DrawMode = DrawMode.OwnerDrawVariable;
cmbMulti.DrawItem += new DrawItemEventHandler(cmbMulti_DrawItem);
cmbMulti.MeasureItem += new MeasureItemEventHandler(cmbMulti_MeasureItem);
cmbMulti.Items.Add("Multiline Text 1");
cmbMulti.Items.Add("Multiline Text 2");
cmbMulti.Items.Add("Multiline Text ");
ToolTip tooltip = new ToolTip();
// Set dropdown height
int totalLines = 0, stringHeight = 0;
foreach (string item in cmbMulti.Items)
{
foreach (string str in WrapString(item, cmbMulti.CreateGraphics(), cmbMulti.Font, cmbMulti.Width))
{
totalLines++;
if (totalLines == 1)
{
stringHeight = (int)cmbMulti.CreateGraphics().MeasureString(item, cmbMulti.Font).Height;
}
}
}
cmbMulti.DropDownHeight = TotalDropdownHeight + 2;
private void cmbMulti_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index > -1)
{
string itemText = cmbMulti.Items[e.Index].ToString();
Brush backgroundBrush, forgroundBrush;
if (e.State == (DrawItemState.Selected | DrawItemState.NoAccelerator | DrawItemState.NoFocusRect) ||
e.State == DrawItemState.Selected)
{
forgroundBrush = new SolidBrush(SystemColors.Highlight);
backgroundBrush = new SolidBrush(SystemColors.Window);
this.tooltip.Show(itemText, cmbMulti, e.Bounds.Right, e.Bounds.Bottom);
}
else
{
forgroundBrush = new SolidBrush(SystemColors.Window);
backgroundBrush = Brushes.Black;
this.tooltip.Hide(cmbMulti);
}
if ((e.State & DrawItemState.Focus) == 0)
{
e.Graphics.FillRectangle(forgroundBrush, e.Bounds);
e.Graphics.DrawString(itemText, Font, backgroundBrush,
e.Bounds);
e.Graphics.DrawRectangle(new Pen(SystemColors.Highlight), e.Bounds);
}
else
{
e.Graphics.FillRectangle(forgroundBrush, e.Bounds);
e.Graphics.DrawString(itemText, Font, backgroundBrush,
e.Bounds);
}
}
}
int TotalDropdownHeight = 0;
protected void cmbMulti_MeasureItem(object sender, MeasureItemEventArgs e)
{
if (e.Index > -1)
{
string itemText = cmbMulti.Items[e.Index].ToString();
SizeF sf = e.Graphics.MeasureString(itemText, Font, cmbMulti.Width);
int multiLineCount = 0;
foreach (string item in WrapString(itemText, e.Graphics, cmbMulti.Font, cmbMulti.Width))
{
multiLineCount++;
}
e.ItemHeight = multiLineCount * 15;
TotalDropdownHeight += e.ItemHeight;
e.ItemWidth = cmbMulti.Width;
}
}
IEnumerable<string> WrapString(string str, Graphics g, Font font,
int allowedWidth)
{
string[] arr = str.Split(' ');
StringBuilder current = new StringBuilder();
foreach (string token in arr)
{
int width =
(int)g.MeasureString(current.ToString() + " " + token, font).Width;
if (width > allowedWidth)
{
yield return current.ToString();
current = new StringBuilder();
}
current.Append(token + " ");
}
yield return current.ToString();
}
It seems like a simple question but how do I set the bacground color of the 'tab control', it seems to be derived from the standard window theme color. Is it Possible to create a black tab control with white text written on the tabs themselves (not the tab page)?
Help, I,m a little familiar with custom controls extending existing controls but I don't know what properties (if they exist) to set.
http://dotnetrix.co.uk/tabcontrol.htm
private void tabControl1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
TabPage CurrentTab = tabControl1.TabPages[e.Index];
Rectangle ItemRect = tabControl1.GetTabRect(e.Index);
SolidBrush FillBrush = new SolidBrush(Color.Red);
SolidBrush TextBrush = new SolidBrush(Color.White);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
//If we are currently painting the Selected TabItem we'll
//change the brush colors and inflate the rectangle.
if (System.Convert.ToBoolean(e.State & DrawItemState.Selected))
{
FillBrush.Color = Color.White;
TextBrush.Color = Color.Red;
ItemRect.Inflate(2, 2);
}
//Set up rotation for left and right aligned tabs
if (tabControl1.Alignment == TabAlignment.Left || tabControl1.Alignment == TabAlignment.Right)
{
float RotateAngle = 90;
if (tabControl1.Alignment == TabAlignment.Left)
RotateAngle = 270;
PointF cp = new PointF(ItemRect.Left + (ItemRect.Width / 2), ItemRect.Top + (ItemRect.Height / 2));
e.Graphics.TranslateTransform(cp.X, cp.Y);
e.Graphics.RotateTransform(RotateAngle);
ItemRect = new Rectangle(-(ItemRect.Height / 2), -(ItemRect.Width / 2), ItemRect.Height, ItemRect.Width);
}
//Next we'll paint the TabItem with our Fill Brush
e.Graphics.FillRectangle(FillBrush, ItemRect);
//Now draw the text.
e.Graphics.DrawString(CurrentTab.Text, e.Font, TextBrush, (RectangleF)ItemRect, sf);
//Reset any Graphics rotation
e.Graphics.ResetTransform();
//Finally, we should Dispose of our brushes.
FillBrush.Dispose();
TextBrush.Dispose();
}
I use something like this in mu TabControl derived class (and it will do gradients too):
protected override void OnDrawItem(DrawItemEventArgs e)
{
// fill in the whole rect
using (SolidBrush br = new SolidBrush(Theme.FormBackColor))
{
e.Graphics.FillRectangle(br, ClientRectangle);
}
// draw the tabs
for (int i = 0; i < TabPages.Count; ++i)
{
TabPage tab = TabPages[i];
// Get the text area of the current tab
RectangleF tabTextArea = (RectangleF)GetTabRect(i);
// determine how to draw the tab based on which type of tab it is
Color tabTopBackColor = GetTopBackColor();
Color tabBottomBackColor = GetBottomBackColor();
Color tabTextColor = GetTextColor();
// draw the background
using (LinearGradientBrush br = new LinearGradientBrush(tabTextArea, tabTopBackColor, tabBottomBackColor, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(br, tabTextArea);
}
// draw the tab header text
using (SolidBrush brush = new SolidBrush(tabTextColor))
{
e.Graphics.DrawString(tab.Text, Font, brush, CreateTabHeaderTextRect(tabTextArea));
}
}
}
private RectangleF CreateTabHeaderTextRect(RectangleF tabTextArea)
{
tabTextArea.X += 3;
tabTextArea.Y += 1;
tabTextArea.Height -= 1;
return tabTextArea;
}