Adding paint code to trackbar control - c#

I want to add text labels onder the tick marks of a trackbar control. Initially everything appears fine but when a drag the thumb of the trackbar my text labels disappear. What is happening here ?
Here is my code :
public class DateTimeTrackBar : TrackBar
{
public DateTimeTrackBar()
{
:
SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
}
:
protected override void OnPaint(PaintEventArgs e)
{
base.SetStyle(ControlStyles.UserPaint, false);
base.Refresh();
if (ShowLabels)
DrawLabels(e);
base.SetStyle(ControlStyles.UserPaint, true);
//base.OnPaint(e);
}
protected virtual void DrawLabels(PaintEventArgs e)
{
int nNumTicks = GetNumTicks(this);
if (nNumTicks > 0)
{
PointF[] TickLocs = GetTickLocations(nNumTicks);
string[] TickLabels = GetTickLabels(nNumTicks);
using (Font ArialFnt = new Font("Arial", 6, FontStyle.Regular))
{
using (Brush GrayBrush = new SolidBrush(Color.Gray))
{
float fTickLabelLocOffset;
for (int i = 0; i < nNumTicks; i++)
{
if (!ShowMinMaxLabels && ((i == 0) || (i == (nNumTicks - 1))))
continue;
if (i == 0)
fTickLabelLocOffset = 0.0f;
else
{
SizeF Size = e.Graphics.MeasureString(TickLabels[i], ArialFnt);
if (i == (nNumTicks - 1))
fTickLabelLocOffset = Size.Width;
else
fTickLabelLocOffset = (Size.Width / 2.0f);
}
PointF TickLabelLoc = new PointF(TickLocs[i].X - fTickLabelLocOffset, TickLocs[i].Y + 8);
e.Graphics.DrawString(TickLabels[i], ArialFnt, GrayBrush, TickLabelLoc);
}
}
}
}
}
:
}

The standard convention here would be to not set the UserPaint style, to call base.OnPaint() in your override method, and then render your custom graphics after the call to base.OnPaint() returns, similar to this:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (ShowLabels)
DrawLabels(e);
}
Based on the documentation for Control.SetStyle() at https://msdn.microsoft.com/en-us/library/system.windows.forms.control.setstyle(v=vs.110).aspx as well as the ControlStyles enumeration at https://msdn.microsoft.com/en-us/library/system.windows.forms.controlstyles(v=vs.110).aspx, consider the following:
You should not be calling SetStyle() repeatedly to change the flags (as happens in your OnPaint() method). These should be set once, when the control is initialized.
When you set the UserPaint flag to true, you are telling Windows the following. This is likely not your intent:
If true, the control paints itself rather than the operating system
doing so. If false, the Paint event is not raised. This style only
applies to classes derived from Control.

Related

Custom Paint in ToolStripTextBox

i have a problem about custom Paint method in class that inherit from ToolStripTextBox.
I need to draw an icon before ToolStripTextBox in ContextMenuStrip.
FIRST ATTEMPT:
ContextMenuStrip CXstrip = new ContextMenuStrip();
CXstrip.ImageList = [My defined list];
var groupMenu = new ToolStripTextBox();
groupMenu.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
groupMenu.ImageIndex = 1;
Failed :( Only textBox will Appear.
SECOND ATTEMPT:
public class ToolStripSpringTextBox : ToolStripTextBox {
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.DrawLine(Pens.Red, 0, 0, 50, 10);
}
}
Paint is called, but he draw always standard textbox without my line (same of First Attempt)
My question is why i cannot draw manually?
Why first (Logic) way to do this doesn't work like ToolStripMenuItem for example?
Thanks.
As a direct answer to your question, the ToolStripTextBox doesn't have in its inheritance tree the ToolStripMenuItem so you can't deal with it in the exact same way.
To assign images to items like ToolStripTextBox, ToolStripComboBox, and ToolStripDropDownItem:
Override the hidden Image property and reverse the attributes to make it visible and accessible again. You will find it in the Properties window to assign an image. Or through the code.
Override the OnParentChanged method to subscribe to the parent's Paint event. The type of the parent of both ContextMenuStrip and a MenuStrip drop down is ToolStripDropDownMenu.
Define the rectangle of the image and draw it.
using System.ComponentModel;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Drawing;
[DesignerCategory("ToolStrip"),
ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.All)]
public class ToolStripSpringTextBox : ToolStripTextBox
{
public ToolStripSpringTextBox() : base() { }
/// <summary>
/// The image that will be displayed on the item.
/// </summary>
[Browsable(true),
EditorBrowsable(EditorBrowsableState.Always),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
Category("Appearance"),
Description("The image associated with the object.")]
public override Image Image { get => base.Image; set => base.Image = value; }
protected override void OnParentChanged(ToolStrip oldParent, ToolStrip newParent)
{
base.OnParentChanged(oldParent, newParent);
if (newParent != null && newParent is ToolStripDropDownMenu)
{
newParent.Paint -= new PaintEventHandler(OnParentPaint);
newParent.Paint += new PaintEventHandler(OnParentPaint);
}
if (oldParent != null)
oldParent.Paint -= new PaintEventHandler(OnParentPaint);
}
private void OnParentPaint(object sender, PaintEventArgs e)
{
if (Image is null ||
(sender is ContextMenuStrip cmnu &&
!cmnu.ShowImageMargin && !cmnu.ShowCheckMargin))
return;
var sz = Image.Size;
var r = new Rectangle((Bounds.X - sz.Width - 7) / 2,
(Bounds.Height - sz.Height) / 2 + Bounds.Y, sz.Width, sz.Height);
if (RightToLeft == RightToLeft.Yes)
r.X = Bounds.Right + r.Width - ContentRectangle.X;
e.Graphics.DrawImage(Image, r);
}
}

TabPage title alignment being wrong after drag'n'dropping

I have the class that extends System.Windows.Forms.TabControl and had implemented drag'n'drop mechanism for its TabPages as following:
#region Overriden base methods
protected override void OnDragOver(DragEventArgs e)
{
if (PointedTabPage == null) return;
e.Effect = DragDropEffects.Move;
var dragTab = e.Data.GetData(typeof (ManagedTabPage)) as ManagedTabPage;
if (dragTab == null) return;
int dropIndex = TabPages.IndexOf(PointedTabPage);
int dragIndex = TabPages.IndexOf(dragTab);
if (dragIndex == dropIndex) return;
var modifiedTabPages = new List<ManagedTabPage>(from ManagedTabPage tabPage in TabPages
where TabPages.IndexOf(tabPage) != dragIndex
select TabPages[TabPages.IndexOf(tabPage)] as ManagedTabPage);
modifiedTabPages.Insert(dropIndex, dragTab);
for (byte i = 0; i < TabPages.Count; ++i)
{
var managedTabPage = TabPages[i] as ManagedTabPage;
if (managedTabPage != null && managedTabPage.Uid == modifiedTabPages[i].Uid)
continue;
TabPages[i] = modifiedTabPages[i];
}
SelectedTab = dragTab;
}
protected override void OnMouseDown(MouseEventArgs e)
{
try
{
switch (e.Button)
{
case MouseButtons.Left:
DoDragDrop(PointedTabPage, DragDropEffects.Move);
break;
case MouseButtons.Middle:
CloseTab(PointedTabPage);
break;
}
}
catch (InvalidOperationException)
{
}
finally
{
TabPages.Insert(0, String.Empty);
TabPages.RemoveAt(0);
}
}
#endregion
Nota bene that in the finally clause of OnMouseDown method there are 2 lines for workarounding the
problem: for some reason w/o these lines after drag'n'dropping any of TabPages alignment of their titles is being wrong:
What should I do to correct this behavior without this smelly workaround? Maybe sending some Windows messages could do the trick?
Thanks a lot for any suggestions.
Edit 1. Code of ManagedTabPage is 100% unimportant (it's just extends TabPage with some specific properties).
PointedTabPage is unimportant too, but this is it:
return (from ManagedTabPage tabPage in TabPages
let tabPageIndex = TabPages.IndexOf(tabPage)
where GetTabRect(tabPageIndex).Contains(PointToClient(Cursor.Position))
select TabPages[tabPageIndex]).Single() as ManagedTabPage;
I'm trying to achieve fully-centered alignment of labels. You see, labels on the screenshot didn't centered horizontally?
I can't do much with the posted code. Let's take a completely different tack and create a tabcontrol that supports dragging a tabpage with the mouse. Add a new class to your project and paste this code:
using System;
using System.Drawing;
using System.Windows.Forms;
class TabControlEx : TabControl {
private Point downPos;
private Form draggingHost;
private Rectangle draggingBounds;
private Point draggingPos;
public TabControlEx() {
this.SetStyle(ControlStyles.UserMouse, true);
}
}
The usage of the variable becomes clear later. First thing we need is to get mouse events from the TabControl so we can see the user trying to drag a tab. That requires turning on the UserMouse control style, it is off by default for controls (like TabControl) that are built-in Windows controls and use their own mouse handling.
Use Build > Build and drag the new control from the top of the toolbox onto a form. Everything still looks and acts like a regular TabControl, but do note that clicking tabs no longer changes the active tab. A side-effect of turning the UserMouse style on. Let's fix that first, paste:
protected override void OnMouseDown(MouseEventArgs e) {
for (int ix = 0; ix < this.TabCount; ++ix) {
if (this.GetTabRect(ix).Contains(e.Location)) {
this.SelectedIndex = ix;
break;
}
}
downPos = e.Location;
base.OnMouseDown(e);
}
We are storing the click location, that's needed later to detect the user dragging the tab. That requires the MouseMove event, we need to start dragging when the user moved the mouse far enough away from the original click position:
protected override void OnMouseMove(MouseEventArgs e) {
if (e.Button == MouseButtons.Left && this.TabCount > 1) {
var delta = SystemInformation.DoubleClickSize;
if (Math.Abs(e.X - downPos.X) >= delta.Width ||
Math.Abs(e.Y - downPos.Y) >= delta.Height) {
startDragging();
}
}
base.OnMouseMove(e);
}
The startDragging method needs to create a toplevel window that can be moved around with the mouse, containing a facsimile of the tab we're dragging around. We'll display it as an owned window, so it is always on top, that has the exact same size as the tab control:
private void startDragging() {
draggingBounds = this.RectangleToScreen(new Rectangle(Point.Empty, this.Size));
draggingHost = createDraggingHost(draggingBounds);
draggingPos = Cursor.Position;
draggingHost.Show(this.FindForm());
}
The createDraggingHost needs to do the heavy lifting and create a window that looks just like the tab. A borderless form we'll move around with the mouse. We'll use the TransparencyKey property to make it look similar to the dragged TabPage with a tab sticking out at the top. And make it look the same by simply letting it display a screenshot of the tabpage:
private Form createDraggingHost(Rectangle bounds) {
var host = new Form() { FormBorderStyle = FormBorderStyle.None, ControlBox = false, AutoScaleMode = AutoScaleMode.None, Bounds = this.draggingBounds, StartPosition = FormStartPosition.Manual };
host.BackColor = host.TransparencyKey = Color.Fuchsia;
var tabRect = this.GetTabRect(this.SelectedIndex);
var tabImage = new Bitmap(bounds.Width, bounds.Height);
using (var gr = Graphics.FromImage(tabImage)) {
gr.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
gr.FillRectangle(Brushes.Fuchsia, new Rectangle(0, 0, tabRect.Left, tabRect.Height));
gr.FillRectangle(Brushes.Fuchsia, new Rectangle(tabRect.Right, 0, bounds.Width - tabRect.Right, tabRect.Height));
}
host.Capture = true;
host.MouseCaptureChanged += host_MouseCaptureChanged;
host.MouseUp += host_MouseCaptureChanged;
host.MouseMove += host_MouseMove;
host.Paint += (s, pe) => pe.Graphics.DrawImage(tabImage, 0, 0);
host.Disposed += delegate { tabImage.Dispose(); };
return host;
}
Note the use of the Capture property, that's how we detect that the user released the mouse button or interrupted the operation by any other means. We'll use the MouseMove event to move the window around:
private void host_MouseMove(object sender, MouseEventArgs e) {
draggingHost.Location = new Point(draggingBounds.Left + Cursor.Position.X - draggingPos.X,
draggingBounds.Top + Cursor.Position.Y - draggingPos.Y);
}
And finally we need to handle the completion of the drag. We'll swap tabs, inserting the dragged tab at the mouse position and destroy the window:
private void host_MouseCaptureChanged(object sender, EventArgs e) {
if (draggingHost.Capture) return;
var pos = this.PointToClient(Cursor.Position);
for (int ix = 0; ix < this.TabCount; ++ix) {
if (this.GetTabRect(ix).Contains(pos)) {
if (ix != this.SelectedIndex) {
var page = this.SelectedTab;
this.TabPages.RemoveAt(this.SelectedIndex);
this.TabPages.Insert(ix, page);
this.SelectedIndex = ix;
}
break;
}
}
draggingHost.Dispose();
draggingHost = null;
}
Looks pretty good.
since you hasn't shared ManagedTabPage code, i used default TabPage control
changes are made in method OnDragOver
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace Demo
{
public class MyTabControl : TabControl
{
public MyTabControl()
{
SizeMode = TabSizeMode.Fixed;
ItemSize = new Size(224, 20);
}
#region Overriden base methods
protected override void OnDragOver(DragEventArgs e)
{
if (DesignMode)
return;
if (PointedTabPage == null) return;
e.Effect = DragDropEffects.Move;
var dragTab = e.Data.GetData(typeof(TabPage)) as TabPage;
if (dragTab == null) return;
int dropIndex = TabPages.IndexOf(PointedTabPage);
int dragIndex = TabPages.IndexOf(dragTab);
if (dragIndex == dropIndex) return;
// change position of tab
TabPages.Remove(dragTab);
TabPages.Insert(dropIndex, dragTab);
SelectedTab = dragTab;
base.OnDragOver(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (DesignMode)
return;
switch (e.Button)
{
case MouseButtons.Left:
DoDragDrop(PointedTabPage, DragDropEffects.Move);
break;
case MouseButtons.Middle:
TabPages.Remove(PointedTabPage);
break;
}
}
#endregion
TabPage PointedTabPage
{
get
{
return TabPages.OfType<TabPage>()
.Where((p, tabPageIndex) => GetTabRect(tabPageIndex).Contains(PointToClient(Cursor.Position)))
.FirstOrDefault();
}
}
}
}

Handle scrolling of a WinForms control manually

I have a control (System.Windows.Forms.ScrollableControl) which can potentially be very large. It has custom OnPaint logic. For that reason, I am using the workaround described here.
public class CustomControl : ScrollableControl
{
public CustomControl()
{
this.AutoScrollMinSize = new Size(100000, 500);
this.DoubleBuffered = true;
}
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
graphics.Clear(this.BackColor);
...
}
}
The painting code mainly draws "normal" things that move when you scroll. The origin of each shape that is drawn is offsetted by this.AutoScrollPosition.
graphics.DrawRectangle(pen, 100 + this.AutoScrollPosition.X, ...);
However, the control also contains "static" elements, which are always drawn at the same position relative to the parent control. For that, I just don't use AutoScrollPosition and draw the shapes directly:
graphics.DrawRectangle(pen, 100, ...);
When the user scrolls, Windows translates the entire visible area in the direction opposite to the scrolling. Usually this makes sense, because then the scrolling seems smooth and responsive (and only the new part has to be redrawn), however the static parts are also affected by this translation (hence the this.Invalidate() in OnScroll). Until the next OnPaint call has successfully redrawn the surface, the static parts are slightly off. This causes a very noticable "shaking" effect when scrolling.
Is there a way I can create a scrollable custom control that does not have this problem with static parts?
You could do this by taking full control of scrolling. At the moment, you're just hooking in to the event to do your logic. I've faced issues with scrolling before, and the only way I've ever managed to get everything to work smoothly is by actually handling the Windows messages by overriding WndProc. For instance, I have this code to synchronize scrolling between several ListBoxes:
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
// 0x115 and 0x20a both tell the control to scroll. If either one comes
// through, you can handle the scrolling before any repaints take place
if (m.Msg == 0x115 || m.Msg == 0x20a)
{
//Do you scroll processing
}
}
Using WndProc will get you the scroll messages before anything gets repainted at all, so you can appropriately handle the static objects. I'd use this to suspend scrolling until an OnPaint occurs. It won't look as smooth, but you won't have issues with the static objects moving.
Since I really needed this, I ended up writing a Control specifically for the case when you have static graphics on a scrollable surface (whose size can be greater than 65535).
It is a regular Control with two ScrollBar controls on it, and a user-assignable Control as its Content. When the user scrolls, the container sets its Content's AutoScrollOffset accordingly. Therefore, it is possible to use controls which use the AutoScrollOffset method for drawing without changing anything. The Content's actual size is exactly the visible part of it at all times. It allows horizontal scrolling by holding down the shift key.
Usage:
var container = new ManuallyScrollableContainer();
var content = new ExampleContent();
container.Content = content;
container.TotalContentWidth = 150000;
container.TotalContentHeight = 5000;
container.Dock = DockStyle.Fill;
this.Controls.Add(container); // e.g. add to Form
Code:
It became a bit lengthy, but I could avoid ugly hacks. Should work with mono. I think it turned out pretty sane.
public class ManuallyScrollableContainer : Control
{
public ManuallyScrollableContainer()
{
InitializeControls();
}
private class UpdatingHScrollBar : HScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private class UpdatingVScrollBar : VScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private ScrollBar shScrollBar;
private ScrollBar svScrollBar;
public ScrollBar HScrollBar
{
get { return this.shScrollBar; }
}
public ScrollBar VScrollBar
{
get { return this.svScrollBar; }
}
private void InitializeControls()
{
this.Width = 300;
this.Height = 300;
this.shScrollBar = new UpdatingHScrollBar();
this.shScrollBar.Top = this.Height - this.shScrollBar.Height;
this.shScrollBar.Left = 0;
this.shScrollBar.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
this.svScrollBar = new UpdatingVScrollBar();
this.svScrollBar.Top = 0;
this.svScrollBar.Left = this.Width - this.svScrollBar.Width;
this.svScrollBar.Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
this.shScrollBar.Width = this.Width - this.svScrollBar.Width;
this.svScrollBar.Height = this.Height - this.shScrollBar.Height;
this.Controls.Add(this.shScrollBar);
this.Controls.Add(this.svScrollBar);
this.shScrollBar.Scroll += this.HandleScrollBarScroll;
this.svScrollBar.Scroll += this.HandleScrollBarScroll;
}
private Control _content;
/// <summary>
/// Specifies the control that should be displayed in this container.
/// </summary>
public Control Content
{
get { return this._content; }
set
{
if (_content != value)
{
RemoveContent();
this._content = value;
AddContent();
}
}
}
private void AddContent()
{
if (this.Content != null)
{
this.Content.Left = 0;
this.Content.Top = 0;
this.Content.Width = this.Width - this.svScrollBar.Width;
this.Content.Height = this.Height - this.shScrollBar.Height;
this.Content.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
this.Controls.Add(this.Content);
CalculateMinMax();
}
}
private void RemoveContent()
{
if (this.Content != null)
{
this.Controls.Remove(this.Content);
}
}
protected override void OnParentChanged(EventArgs e)
{
// mouse wheel events only arrive at the parent control
if (this.Parent != null)
{
this.Parent.MouseWheel -= this.HandleMouseWheel;
}
base.OnParentChanged(e);
if (this.Parent != null)
{
this.Parent.MouseWheel += this.HandleMouseWheel;
}
}
private void HandleMouseWheel(object sender, MouseEventArgs e)
{
this.HandleMouseWheel(e);
}
/// <summary>
/// Specifies how the control reacts to mouse wheel events.
/// Can be overridden to adjust the scroll speed with the mouse wheel.
/// </summary>
protected virtual void HandleMouseWheel(MouseEventArgs e)
{
// The scroll difference is calculated so that with the default system setting
// of 3 lines per scroll incremenet,
// one scroll will offset the scroll bar value by LargeChange / 4
// i.e. a quarter of the thumb size
ScrollBar scrollBar;
if ((Control.ModifierKeys & Keys.Shift) != 0)
{
scrollBar = this.HScrollBar;
}
else
{
scrollBar = this.VScrollBar;
}
var minimum = 0;
var maximum = scrollBar.Maximum - scrollBar.LargeChange;
if (maximum <= 0)
{
// happens when the entire area is visible
return;
}
var value = scrollBar.Value - (int)(e.Delta * scrollBar.LargeChange / (120.0 * 12.0 / SystemInformation.MouseWheelScrollLines));
scrollBar.Value = Math.Min(Math.Max(value, minimum), maximum);
}
public event ScrollEventHandler Scroll;
protected virtual void OnScroll(ScrollEventArgs e)
{
var handler = this.Scroll;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Event handler for the Scroll event of either scroll bar.
/// </summary>
private void HandleScrollBarScroll(object sender, ScrollEventArgs e)
{
OnScroll(e);
if (this.Content != null)
{
this.Content.AutoScrollOffset = new System.Drawing.Point(-this.HScrollBar.Value, -this.VScrollBar.Value);
this.Content.Invalidate();
}
}
private int _totalContentWidth;
public int TotalContentWidth
{
get { return _totalContentWidth; }
set
{
if (_totalContentWidth != value)
{
_totalContentWidth = value;
CalculateMinMax();
}
}
}
private int _totalContentHeight;
public int TotalContentHeight
{
get { return _totalContentHeight; }
set
{
if (_totalContentHeight != value)
{
_totalContentHeight = value;
CalculateMinMax();
}
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
CalculateMinMax();
}
private void CalculateMinMax()
{
if (this.Content != null)
{
// Reduced formula according to
// http://msdn.microsoft.com/en-us/library/system.windows.forms.scrollbar.maximum.aspx
// Note: The original formula is bogus.
// According to the article, LargeChange has to be known in order to calculate Maximum,
// however, that is not always possible because LargeChange cannot exceed Maximum.
// If (LargeChange) == (1 * visible part of control), the formula can be reduced to:
if (this.TotalContentWidth > this.Content.Width)
{
this.shScrollBar.Enabled = true;
this.shScrollBar.Maximum = this.TotalContentWidth;
}
else
{
this.shScrollBar.Enabled = false;
}
if (this.TotalContentHeight > this.Content.Height)
{
this.svScrollBar.Enabled = true;
this.svScrollBar.Maximum = this.TotalContentHeight;
}
else
{
this.svScrollBar.Enabled = false;
}
// this must be set after the maximum is determined
this.shScrollBar.LargeChange = this.shScrollBar.Width;
this.shScrollBar.SmallChange = this.shScrollBar.LargeChange / 10;
this.svScrollBar.LargeChange = this.svScrollBar.Height;
this.svScrollBar.SmallChange = this.svScrollBar.LargeChange / 10;
}
}
}
Example content:
public class ExampleContent : Control
{
public ExampleContent()
{
this.DoubleBuffered = true;
}
static Random random = new Random();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
// random color to make the clip rectangle visible in an unobtrusive way
var color = Color.FromArgb(random.Next(160, 180), random.Next(160, 180), random.Next(160, 180));
graphics.Clear(color);
Debug.WriteLine(this.AutoScrollOffset.X.ToString() + ", " + this.AutoScrollOffset.Y.ToString());
CheckerboardRenderer.DrawCheckerboard(
graphics,
this.AutoScrollOffset,
e.ClipRectangle,
new Size(50, 50)
);
StaticBoxRenderer.DrawBoxes(graphics, new Point(0, this.AutoScrollOffset.Y), 100, 30);
}
}
public static class CheckerboardRenderer
{
public static void DrawCheckerboard(Graphics g, Point origin, Rectangle bounds, Size squareSize)
{
var numSquaresH = (bounds.Width + squareSize.Width - 1) / squareSize.Width + 1;
var numSquaresV = (bounds.Height + squareSize.Height - 1) / squareSize.Height + 1;
var startBoxH = (bounds.X - origin.X) / squareSize.Width;
var startBoxV = (bounds.Y - origin.Y) / squareSize.Height;
for (int i = startBoxH; i < startBoxH + numSquaresH; i++)
{
for (int j = startBoxV; j < startBoxV + numSquaresV; j++)
{
if ((i + j) % 2 == 0)
{
Random random = new Random(i * j);
var color = Color.FromArgb(random.Next(70, 95), random.Next(70, 95), random.Next(70, 95));
var brush = new SolidBrush(color);
g.FillRectangle(brush, i * squareSize.Width + origin.X, j * squareSize.Height + origin.Y, squareSize.Width, squareSize.Height);
brush.Dispose();
}
}
}
}
}
public static class StaticBoxRenderer
{
public static void DrawBoxes(Graphics g, Point origin, int boxWidth, int boxHeight)
{
int height = origin.Y;
int left = origin.X;
for (int i = 0; i < 25; i++)
{
Rectangle r = new Rectangle(left, height, boxWidth, boxHeight);
g.FillRectangle(Brushes.White, r);
g.DrawRectangle(Pens.Black, r);
height += boxHeight;
}
}
}

Making a control transparent

I am currently developing a simple image editing tool using Winforms and .NET 3.5 (work environment).
I have a requirement that when the user clicks a select tool button, a square (rectangle in C#) will appear that they can scale between 100x100 and 400x400. I have this bit fixed - the issue comes with making the background of the rectangle transparent.
I'm a little unclear on if transparency is supported in .NET 3.5, I've tried the following:
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
pnlSelectArea.BackColor = Color.Transparent;
pnlSelectArea.ForeColor = Color.Transparent;
selectArea1.BackColor = Color.Transparent;
selectArea1.ForeColor = Color.Transparent;
But this has no effect - any advice would be appreciated.
This is my special Control which contains an opacity property, it 100% works:
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
public class TranspCtrl : Control
{
public bool drag = false;
public bool enab = false;
private int m_opacity = 100;
private int alpha;
public TranspCtrl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Opaque, true);
this.BackColor = Color.Transparent;
}
public int Opacity
{
get
{
if (m_opacity > 100)
{
m_opacity = 100;
}
else if (m_opacity < 1)
{
m_opacity = 1;
}
return this.m_opacity;
}
set
{
this.m_opacity = value;
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = cp.ExStyle | 0x20;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle bounds = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
Color frmColor = this.Parent.BackColor;
Brush bckColor = default(Brush);
alpha = (m_opacity * 255) / 100;
if (drag)
{
Color dragBckColor = default(Color);
if (BackColor != Color.Transparent)
{
int Rb = BackColor.R * alpha / 255 + frmColor.R * (255 - alpha) / 255;
int Gb = BackColor.G * alpha / 255 + frmColor.G * (255 - alpha) / 255;
int Bb = BackColor.B * alpha / 255 + frmColor.B * (255 - alpha) / 255;
dragBckColor = Color.FromArgb(Rb, Gb, Bb);
}
else
{
dragBckColor = frmColor;
}
alpha = 255;
bckColor = new SolidBrush(Color.FromArgb(alpha, dragBckColor));
}
else
{
bckColor = new SolidBrush(Color.FromArgb(alpha, this.BackColor));
}
if (this.BackColor != Color.Transparent | drag)
{
g.FillRectangle(bckColor, bounds);
}
bckColor.Dispose();
g.Dispose();
base.OnPaint(e);
}
protected override void OnBackColorChanged(EventArgs e)
{
if (this.Parent != null)
{
Parent.Invalidate(this.Bounds, true);
}
base.OnBackColorChanged(e);
}
protected override void OnParentBackColorChanged(EventArgs e)
{
this.Invalidate();
base.OnParentBackColorChanged(e);
}
}
You will need to use Opacity property and set it to zero to make form invisible.
If you want to make a control Transparent, as you have tried in your example, See this article
How to: Give Your Control a Transparent Background
It say the code you have written, must be in constructor of the control. Hence, I guess, you will need to create a custom control derived from your pnlSelectArea 's type most probaably a button. In in that custom control's constructor you can write code to set its style and color.
Here is what worked for me with because the other solutions did not work.
This is with transparent UserControl added to ListView/TreeView Control Collection
I know it says ButtonRenderer but it should work for any controls.
In the UserControl:
protected override void OnPaint(PaintEventArgs e)
{
ButtonRenderer.DrawParentBackground(e.Graphics, this.ClientRectangle, this);
}
in the Parent control:
protected override void WndProc(ref Message m)
{
if(m.Msg == 0xF)
foreach(Control c in this.Controls) { c.Invalidate(); c.Update(); }
base.WndProc(ref m);
}
great!!
I finally managed to draw transparent shapes.
I've added a virtual method
Draw(g);
right before
bckColor.Dispose();
g.Dispose();
base.OnPaint(e);
and at the end the declaration of the virtual method
protected virtual void Draw(Graphics g){ }
Now I can continue creating my own Transparent shapes, graphics etc ...
There is one simple workaround for this. You can create an image with a transparent background (PNG) and add it for the Image property of the icon. This works fine as information does not have much flexibility in styling. Sometime this might not be suitable for everyone. Remember this is only a workaround.
PS:
Add where ever the text on the image and keep blank for the text property.

How come the graphics is not drawing onto the panel

I want to draw a grid in my panel. The graphics object (screen) that i created with bitmap isn't drawing in my panel. I tried with debugging to look if the screen wasn't drawing but that wasn't the case.
I tried creating the graphic object from the panel createGraphic method and the parameter painteventargs from the panel paint method. Both times when I used it to draw it with OnPaint it took too long.
public Main()
{
InitializeComponent();
backBuffer = new Bitmap(drawPanel.Width, drawPanel.Height);
screen = Graphics.FromImage(backBuffer);
sizeGridPoints = 2;
lenghtBetweenGridPoints = 10;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
screen.Clear(Color.Black);
DrawGrid();
}
private void DrawGrid()
{
for(int x = lenghtBetweenGridPoints; x < drawPanel.Width; x += lenghtBetweenGridPoints)
{
for(int y = lenghtBetweenGridPoints; y < drawPanel.Height; y+= lenghtBetweenGridPoints)
{
screen.FillEllipse(new SolidBrush(Color.Green), x, y, sizeGridPoints, sizeGridPoints);
}
}
}
If you create a Graphics object from a bitmap, it will draw on this bitmap, not on your user interface. Instead, use the Graphics object from the PaintEventArgs e of your OnPaint method, to draw directly on the form or a control.
e.Graphics.FillEllipse(new SolidBrush(Color.Green), x, y, sizeGridPoints, sizeGridPoints);
You should never create your own Graphics object.
Create your own grid control:
public class GridPanel : Panel
{
public GridPanel()
{
DoubleBuffered = true; // Speeds up drawing, e.g. when panel is resized.
// Set default colors
BackColor = Color.Black;
ForeColor = Color.Green;
}
private int _lenghtBetweenGridPoints = 20;
public int LenghtBetweenGridPoints
{
get { return _lenghtBetweenGridPoints; }
set {
if (value != _lenghtBetweenGridPoints) {
_lenghtBetweenGridPoints = value;
Invalidate(); // Redraw the grid.
}
}
}
private int _sizeGridPoints = 3;
public int SizeGridPoints
{
get {
return _sizeGridPoints;
}
set {
if (value != _sizeGridPoints) {
_sizeGridPoints = value;
Invalidate(); // Redraw the grid.
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
// e.Graphics.Clear(Color.Black); Not necessary. We use the BackColor of the panel.
if (LenghtBetweenGridPoints > 0 && SizeGridPoints > 0) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; // Optional.
using (var brush = new SolidBrush(ForeColor)) { // We use the ForeColor of the panel.
for (int x = LenghtBetweenGridPoints; x < Width; x += LenghtBetweenGridPoints) {
for (int y = LenghtBetweenGridPoints; y < Height; y += LenghtBetweenGridPoints) {
e.Graphics.FillEllipse(brush, x, y, SizeGridPoints, SizeGridPoints);
}
}
}
}
}
}
Once it has been compiled, it will automatically appear in toolbox window and you can drag and drop it on your form. You will even be able to edit the properties LenghtBetweenGridPoints and SizeGridPoints in the properties window.
You could also simply use the already available BackColor and ForeColor properties of the panel for the grid. This would allow you to set the colors in the properties window as well. Don't forget to dispose brushes that you have created.
Important: Do not call OnPaint directly. Instead, call the Invalidate or Refresh methods of the object you want to redraw. The point is that Windows decides when to call OnPaint. E.g. if Invalidate is called too frequently (e.g. 5 times in 1/60 seconds), Windows might decide to not call OnPaint every time, as this would create lag. On the other hand, when the user resizes the panel, Windows will call OnPaint automatically. If you restore a window that was minimized, this will re-paint the control as well. Otherwise it would remain black.

Categories