I'm creating a custom ToolTip that will bold the first line of text if the text is multi-line. I'm also using the VisualStyleRenderer to draw the tool tip correctly with styles. However, when I draw the text (even with TextFormatFlags.VerticalCenter set), it draws at the top of the box. I was going to just bump the bounding box down 2 pixels (which fixed it on Windows 7) but I wasn't 100% sure how portable that would be to another OS. Does anyone know how to draw the text vertically centered correctly?
EDIT: To make this clear, I know this code doesn't bold the first line. I'm trying to first replicate a standard tooltip, and then afterwards do the bolding.
public class BoldedFirstLineToolTip : ToolTip
{
public BoldedFirstLineToolTip()
{
this.OwnerDraw = true;
this.Draw += new DrawToolTipEventHandler(OnDraw);
}
private void OnDraw(object sender, DrawToolTipEventArgs e)
{
// Try to draw using the visual style renderer.
if (VisualStyleRenderer.IsElementDefined(VisualStyleElement.ToolTip.Standard.Normal))
{
var renderer = new VisualStyleRenderer(VisualStyleElement.ToolTip.Standard.Normal);
renderer.DrawBackground(e.Graphics, e.Bounds);
var b = e.Bounds;
// b.Y + 2 // This works when using e.Graphics.DrawString.
renderer.DrawText(e.Graphics, b, e.ToolTipText, false /*drawDisabled*/,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
else
{
// Fall back to non-visual style drawing.
e.DrawBackground();
e.DrawBorder();
e.DrawText();
}
}
}
I've decided to just use padding fixes. I've provided my full solution below. I tested on both XP and Windows 7.
public class BoldedFirstLineToolTip : ToolTip
{
public BoldedFirstLineToolTip()
{
this.OwnerDraw = true;
this.Draw += new DrawToolTipEventHandler(OnDraw);
}
private void OnDraw(object sender, DrawToolTipEventArgs e)
{
// Try to draw using the visual style renderer.
if (VisualStyleRenderer.IsSupported && VisualStyleRenderer.IsElementDefined(VisualStyleElement.ToolTip.Standard.Normal))
{
var bounds = e.Bounds;
var renderer = new VisualStyleRenderer(VisualStyleElement.ToolTip.Standard.Normal);
renderer.DrawBackground(e.Graphics, bounds);
var color = renderer.GetColor(ColorProperty.TextColor);
var text = e.ToolTipText;
using (var textBrush = new SolidBrush(renderer.GetColor(ColorProperty.TextColor)))
using (var font = e.Font)
{
// Fix the positioning of the bounds for the text rectangle.
var rendererBounds = new Rectangle(e.Bounds.X + 6, e.Bounds.Y + 2, e.Bounds.Width - 6 * 2, e.Bounds.Height - 2 * 2);
if (!text.Contains('\n'))
{
renderer.DrawText(e.Graphics, rendererBounds, text);
}
else
{
var lines = text.Split('\n').Select(l => l.Trim());
var first = lines.First();
var otherLines = Environment.NewLine + String.Join(Environment.NewLine, lines.Skip(1).ToArray());
// Draw the first line.
using (var boldFont = new Font(font, FontStyle.Bold))
{
e.Graphics.DrawString(first, boldFont, textBrush, rendererBounds.X - 1, rendererBounds.Y - 1);
}
renderer.DrawText(e.Graphics, rendererBounds, otherLines, false /*drawDisabled*/, TextFormatFlags.Left);
}
}
}
else
{
// Fall back to non-visual style drawing.
e.DrawBackground();
e.DrawBorder();
using (var sf = new StringFormat())
{
sf.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(e.ToolTipText, SystemFonts.DialogFont, Brushes.Black, e.Bounds, sf);
}
}
}
}
Related
I am creating a project that needs to have a vertical scroll bar with multiple pictures like the server explorer in discord:
For example:
how can I mimic in WinForms in C# (not only having pictures scrolling but also the pictures can have events attached to them?
First you need add a Parent Panel, i'm used the pnServers.
Set property value:
AutoScroll = True;
On Code Behind you can create a List of Rounded Pictures.
private void DiscordServerBarExample_Load(object sender, System.EventArgs e)
{
// Example, in your case this looping is based on return (Database, Api, ...).
for (int i = 1; i <= 10; i++)
{
Panel pnServer = new Panel()
{
Dock = DockStyle.Top,
Height = pnServers.Width,
Padding = new Padding(10)
};
RoundedPictureBox serverImage = new RoundedPictureBox()
{
SizeMode = PictureBoxSizeMode.CenterImage,
Dock = DockStyle.Fill
};
serverImage.Image = Properties.Resources._255352;
pnServer.Controls.Add(serverImage);
pnServers.Controls.Add(pnServer);
}
}
Rounded Picture Box Code:
public class RoundedPictureBox : PictureBox
{
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddEllipse(0, 0, Width - 1, Height - 1);
Region rg = new Region(gp);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawEllipse(new Pen(new SolidBrush(this.BackColor), 1), 0, 0, this.Width - 1, this.Height - 1);
Region = rg;
}
}
}
And this is final result.
I'm drawing a custom toolstrip using ToolStripProfessionalRender and editing the OnRenderItemText event as follows:
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
e.Item.ForeColor = Clr.White;
e.Item.TextAlign = ContentAlignment.MiddleLeft;
e.Item.Alignment = ToolStripItemAlignment.Left;
base.OnRenderItemText(e);
if (e.Item.GetType() == typeof(ToolStripDropDownButton))
{
ToolStripDropDownButton tsmi = (ToolStripDropDownButton)e.Item;
if (tsmi.HasDropDownItems && tsmi.OwnerItem == null)
{
Rectangle bounds = tsmi.Bounds;
bounds.X = bounds.Right - 25;
bounds.Width = 25;
bounds.Y = 10;
// Draw the corner
Graphics G = e.Graphics;
SolidBrush brushw = new SolidBrush(Color.FromArgb(70,70,70));
Point[] points =
{
new Point(bounds.Right - 3, bounds.Height - 11), // point top right
new Point(bounds.Right - 3, bounds.Bottom - 14), // point bottom right
new Point(bounds.Right - 10, bounds.Bottom - 14) // point bottom left
};
G.FillPolygon(brushw, points);
}
}
}
and basically the output i'm trying to obtain is the following:
So drawing a little triangle on the bottom right corner when i got a ToolStripDropDownButton. The problem is that the little triangle is drawn only first item.
To end up the question i draw this toolstrip dynamically using a function that adds a dropdownbutton at each call.
ToolStripDropDownButton m_Item = new ToolStripDropDownButton(text, image);
m_Item.ImageAlign = ContentAlignment.MiddleCenter;
m_Item.ImageScaling = ToolStripItemImageScaling.None;
m_Item.Name = name;
m_Item.ForeColor = Color.White;
m_Item.BackColor = Color.FromArgb(95, 95, 95);
m_Item.Padding = new Padding(5);
m_Item.ShowDropDownArrow = false;
m_Item.Paint += new PaintEventHandler(this.PaintButtonBorder);
if (tabPage != null)
m_Item.Click += (sender, e) => AddClickTab(sender, e, tabPage);
((ToolStripDropDownMenu)m_Item.DropDown).ShowImageMargin = false;
((ToolStripDropDownMenu)m_Item.DropDown).ShowCheckMargin = false;
((ToolStripDropDownMenu)m_Item.DropDown).Cursor = Cursors.Hand;
toolStrip1.Items.Add(m_Item);
if (SubItems != null)
{
for(int i = 0; i < SubItems.Length; i++)
{
object[] subitem = (object[])SubItems[i];
FnAddToolStripMenuItem(
subitem[0].ToString(),
subitem[1].ToString(),
(Bitmap)subitem[2],
m_Item,
(TabPage)subitem[3]
);
}
}
Am i missing some "new" maybe?
Override the OnRenderItemText method only to draw the text part as it says, and/or to set the default properties used when rendering the text. To change the look and the shape of the arrows of the dropdown items, override the OnRenderArrow method.
Example
using System.Drawing;
using System.Drawing.Drawing2D;
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
{
// Optional: to be the default color of the arrows.
e.ArrowColor = Color.FromArgb(70, 70, 70);
if (e.Item is ToolStripDropDownButton item && item.OwnerItem == null)
{
var g = e.Graphics;
var r = new Rectangle(item.Bounds.Width - 10, item.Bounds.Height - 10, 8, 8);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.Half;
using (var br = new SolidBrush(e.ArrowColor))
g.FillPolygon(br, new[]
{
new Point(r.Left, r.Bottom),
new Point(r.Right, r.Top),
new Point(r.Right, r.Bottom)
});
g.SmoothingMode = SmoothingMode.None;
g.PixelOffsetMode = PixelOffsetMode.Default;
}
else
base.OnRenderArrow(e);
}
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
e.Item.ForeColor = Color.White;
e.Item.TextAlign = ContentAlignment.MiddleLeft;
e.Item.Alignment = ToolStripItemAlignment.Left;
base.OnRenderItemText(e);
}
Make sure to enable the ShowDropDownArrow property of the dropdown buttons. So comment this m_Item.ShowDropDownArrow = false;.
If you are also interested to change the color according to the current state of the dropdown button (Selected, Pressed), then you can do for example:
using (var br = new SolidBrush(item.Selected
? Color.FromArgb(150, 150, 150) : item.Pressed
? Color.FromArgb(100, 100, 100) :
Color.FromArgb(70, 70, 70)))
//...
I am nearly there with this ... :)
I have implemented my own double buffer ...
So I create a bitmap:
if (_Bitmap != null) _Bitmap.Dispose();
if (_Graphics != null) _Graphics.Dispose();
_Bitmap = new Bitmap(Bounds.Width, Bounds.Height);
_Bitmap.MakeTransparent(Color.Transparent);
_Graphics = Graphics.FromImage(_Bitmap);
_Graphics.FillRectangle(Brushes.Transparent, Bounds);
I thought that I might have to manually set the bitmap as transparent.
In my handlers OnPaint method it does this:
protected override void OnPaint(PaintEventArgs e)
{
if (_pDevice != null)
{
try
{
_pDevice.update();
_Graphics.ReleaseHdc();
if (_bZoomWindow)
{
//_Graphics.DrawRectangle(_selectionPen, _rcRubberBand);
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddRectangle(_rcRubberBand);
gp.Widen(_selectionPen);
_Graphics.FillPath(Brushes.WhiteSmoke, gp);
}
}
OdRxDictionary Properties = _graphicsDevice.properties();
//if (helperDevice.UnderlyingDevice.Properties.Contains("WindowHDC"))
// helperDevice.UnderlyingDevice.Properties.AtPut("WindowHDC", new RxVariant((Int32)graphics.GetHdc()));
if (Properties.ContainsKey("WindowHDC"))
Properties.putAt("WindowHDC", new OdRxVariantValue(_Graphics.GetHdc().ToInt32())); // hWnd necessary for DirectX device
}
catch (System.Exception ex)
{
_Graphics.DrawString(ex.ToString(), new Font("Arial", 16), new SolidBrush(Color.Black), new PointF(150.0F, 150.0F));
}
e.Graphics.DrawImageUnscaled(_Bitmap, 0, 0);
}
}
The problem is that the rectangle is drawing with a black background. So it is obliterating the drawing underneath that is on the bitmap:
How do I draw just the rectangle? What am I missing? I am sorry if this is a dumb question!
Painting with transparency is unfortunately only supported in one way: By applying the RGB channels in the strenght of the alpha value.
With alpha = 0 nothing happens.
Other modes are desirable but not supported in GDI+ drawing.
One simple workaround is to turn off antialiasing, draw in a weird color you don't need and then call MakeTransparent.
Bitmap bmp = new Bitmap(244, 244, PixelFormat.Format32bppArgb);
Color funnyColor = Color.FromArgb(255, 123, 45, 67);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.LawnGreen);
using (SolidBrush br = new SolidBrush(funnyColor ))
{
// no anti-aliased pixels!
g.SmoothingMode = SmoothingMode.None;
// draw your stuff..
g.FillEllipse( br , 14, 14, 88, 88);
}
bmp.MakeTransparent(funnyColor );
// do what you want..
bmp.Save(someFileName, ImageFormat.Png);
}
Of course you can use all DrawXXX methods including FillPath or DrawRectangle.
The result is a green bitmap with a transparent hole in it. Here it is in Paint.Net:
For other modes, that maybe would copy the alpha channel or mix it with the previous value you would have to write routines of your own or find a lib that has it, but I think this should be all you need atm.
Edit by Andrew Truckle
The proposed answer is really good. However, since I was using Teigha.Net as the basis of the application, in the end I went with this code:
protected override void OnMouseMove(MouseEventArgs e)
{
if (_bZoomWindowing)
UpdateRubberBandRectangle(e.Location);
if (_bPanWindowMode)
UpdateRubberBandLine(e.Location);
base.OnMouseMove(e);
}
private void UpdateRubberBandRectangle(Point Location)
{
// Do we need to erase the old one?
if (!_rcLastRubberBand.IsEmpty)
{
using (Region r = new Region(Rectangle.Inflate(_rcLastRubberBand, 2, 2)))
{
r.Exclude(Rectangle.Inflate(_rcLastRubberBand, -2, -2));
_pDevice.invalidate(new OdGsDCRect(_rcLastRubberBand.Left - 2, _rcLastRubberBand.Right + 2,
_rcLastRubberBand.Top - 2, _rcLastRubberBand.Bottom + 2));
Invalidate(r);
}
}
// Draw the new one
if (!_selectionStart.IsEmpty && !_selectionEnd.IsEmpty && _selectionEnd != Location)
{
_rcLastRubberBand = _rcRubberBand;
_selectionEnd = Location;
_rcRubberBand = GetSelectionRectangle(_selectionStart, _selectionEnd);
using (Region r = new Region(Rectangle.Inflate(_rcRubberBand, 2, 2)))
{
r.Exclude(Rectangle.Inflate(_rcRubberBand, -2, -2));
_pDevice.invalidate(new OdGsDCRect(_rcRubberBand.Left - 2, _rcRubberBand.Right + 2,
_rcRubberBand.Top - 2, _rcRubberBand.Bottom + 2));
Invalidate(r);
}
}
}
private void UpdateRubberBandLine(Point Location)
{
// Do we need to erase the last rubber band line? (Rectangle already expanded by 2 pixels)
if (!_rcLastRubberBandLine.IsEmpty)
{
using (Region r = new Region(_rcLastRubberBandLine))
{
_pDevice.invalidate(new OdGsDCRect(_rcLastRubberBandLine.Left, _rcLastRubberBandLine.Right,
_rcLastRubberBandLine.Top, _rcLastRubberBandLine.Bottom));
Invalidate(r);
}
}
// Draw the new one now
_RubberLineEnd = Location;
_rcLastRubberBandLine = GetSelectionRectangle(_RubberLineStart, _RubberLineEnd);
_rcLastRubberBandLine.Inflate(2, 2);
using (Region r = new Region(_rcLastRubberBandLine))
{
_pDevice.invalidate(new OdGsDCRect(_rcLastRubberBandLine.Left, _rcLastRubberBandLine.Right,
_rcLastRubberBandLine.Top, _rcLastRubberBandLine.Bottom));
Invalidate(r);
}
}
Notice that I am making use of Region objects. Also, the invalidating is being handled by OdGsDevice _pDevice which is a Teigha object. In my situation this worked fabulously.
I currently have a Windows Form project where I have created a simple chat application. Currently the chat is output to a multi line text box, however I want to now enhance it a little and add some styling. In time I wish to have some image, format it nicely and perhaps some HTML (although this isnt vital) in the future. I am just wondering what I should use to achieve this. I did think of updating a HTML page and then reload it with each new message, but this wouldn't give a very good user experience. I have also looked at the richtextbox class but this seems to be a little limited for what I am after. I am hoping some one can point me in the right direction as to what to use.
I am trying to achieve something similar as what I have highlighted in red:
While some of the other comments indicate that WPF is well suited to this, in the real world, it is not always possible or desirable to switch.
A regular owner-drawn list box is well suited to this purpose.
To create one, simply set the DrawMode on the list box to OwnerDrawVariable, e.g.
list.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
Then you just need to provide two event handlers, the first to measure the item (tell the list box how tall the item will be), and another to actually render it. e.g.
this.list.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.list_DrawItem);
this.list.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.list_MeasureItem);
Rendering an image into the list is fairly simple with GDI+ DrawImage (where g is your graphics context_:
Bitmap bmp = Bitmap.FromFile("test.jpg");
Rectangle source = new Rectangle(0, 0, bmp.Width, bmp.Height);
Rectangle dest = source;
g.DrawImage(bmp, dest, source, GraphicsUnit.Pixel);
This is a sample Windows Form which has an owner-drawn list box of all fonts on the system, producing variable-height owner-drawn list items:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Font_Display
{
public class Test : System.Windows.Forms.Form
{
private Font head;
private System.Windows.Forms.ListBox list;
private System.ComponentModel.Container components = null;
public Test()
{
InitializeComponent();
head = new Font("Arial", 10, GraphicsUnit.Pixel);
}
protected override void Dispose(bool disposing)
{
if (disposing) {
if (components != null) {
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.list = new System.Windows.Forms.ListBox();
this.SuspendLayout();
//
// list
//
this.list.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
this.list.IntegralHeight = false;
this.list.Location = new System.Drawing.Point(12, 12);
this.list.Name = "list";
this.list.Size = new System.Drawing.Size(604, 323);
this.list.TabIndex = 0;
this.list.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.list_DrawItem);
this.list.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.list_MeasureItem);
//
// Test
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);
this.ClientSize = new System.Drawing.Size(520, 358);
this.Controls.Add(this.list);
this.Name = "Test";
this.Text = "Display";
this.Load += new System.EventHandler(this.Test_Load);
this.Resize += new System.EventHandler(this.Display_Resize);
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Test());
}
private void Test_Load(object sender, EventArgs e)
{
try {
// Loop all font families
FontFamily[] families = FontFamily.Families;
foreach (FontFamily family in families) {
try { list.Items.Add(new Font(family, 20, FontStyle.Regular, GraphicsUnit.Pixel)); continue; }
catch { }
}
Display_Resize(this, EventArgs.Empty);
}
catch {
}
}
private void Display_Resize(object sender, System.EventArgs e)
{
Rectangle r = this.ClientRectangle;
list.SetBounds(list.Left,
list.Top,
r.Width - (list.Left * 2),
r.Height - (list.Top + list.Left));
}
public string TextValue = "Example String";
public StringFormat Format
{
get
{
StringFormat format = StringFormat.GenericTypographic;
format.FormatFlags |= StringFormatFlags.NoWrap;
return format;
}
}
private void list_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
Brush back = null;
Brush fore = null;
Brush htext = null;
Rectangle r;
try {
Font font = (Font)list.Items[e.Index];
// Loop
if ((e.State & DrawItemState.Selected) != 0) {
back = new SolidBrush(Color.DarkBlue);
fore = new SolidBrush(Color.White);
htext = new SolidBrush(Color.Orange);
}
else {
back = new SolidBrush(Color.White);
fore = new SolidBrush(Color.Black);
htext = new SolidBrush(Color.DarkRed);
}
// Fill the rect
e.Graphics.FillRectangle(back, e.Bounds);
// Get the size of the header
SizeF szHeader = e.Graphics.MeasureString(font.Name, head, int.MaxValue, Format);
SizeF szText = e.Graphics.MeasureString(TextValue, font, int.MaxValue, Format);
// Draw the string
r = e.Bounds;
r.Height = (int)szHeader.Height;
e.Graphics.DrawString(font.Name, head, htext, r, Format);
// Draw the string
r = e.Bounds;
r.Y = (int)(e.Bounds.Y + szHeader.Height);
r.Height = (int)szText.Height;
e.Graphics.DrawString(TextValue, font, fore, r, Format);
}
catch {
}
finally {
if (fore != null) fore.Dispose();
if (back != null) back.Dispose();
if (htext != null) htext.Dispose();
}
}
private void list_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e)
{
try {
Font font = (Font)list.Items[e.Index];
SizeF szHeader = e.Graphics.MeasureString(font.Name, head, int.MaxValue, Format);
SizeF szText = e.Graphics.MeasureString(TextValue, font, int.MaxValue, Format);
// Return it
e.ItemHeight = (int)(szText.Height + szHeader.Height);
e.ItemWidth = (int)Math.Max(szText.Width, szHeader.Width);
}
catch {
}
}
}
}
I have the following code under a TabConttrols DrawItem event that I am trying to extract into a class file. I am having trouble since it is tied to an event. Any hints or pointers would be greatly appreciated.
private void tabCaseNotes_DrawItem(object sender, DrawItemEventArgs e)
{
TabPage currentTab = tabCaseNotes.TabPages[e.Index];
Rectangle itemRect = tabCaseNotes.GetTabRect(e.Index);
SolidBrush fillBrush = new SolidBrush(Color.Linen);
SolidBrush textBrush = new SolidBrush(Color.Black);
StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center,
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.LightSteelBlue;
textBrush.Color = Color.Black;
itemRect.Inflate(2, 2);
}
//Set up rotation for left and right aligned tabs
if (tabCaseNotes.Alignment == TabAlignment.Left || tabCaseNotes.Alignment == TabAlignment.Right)
{
float rotateAngle = 90;
if (tabCaseNotes.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();
}
Depends on what you're trying to achieve. You could always sub class TabControl or you could encapsulate the drawing code in a class that you pass a TabControl to.
public class TabRenderer
{
private TabControl _tabControl;
public TabRenderer(TabControl tabControl)
{
_tabControl = tabControl;
_tabControl.DrawMode = TabDrawMode.OwnerDrawFixed;
_tabControl.DrawItem += TabControlDrawItem;
}
private void TabControlDrawItem(object sender, DrawItemEventArgs e)
{
// Your drawing code...
}
}