C# override OnDrawItem - c#

I am making a trying to make this combo box: see picture attached. For the LineStyle combo box.
Here's the code I have so far
public partial class frmDlgGraphOptions : Form
public partial class frmDlgGraphOptions : Form
{
public frmDlgGraphOptions()
{
InitializeComponent();
CmbBoxlineStyles.DropDownStyle = ComboBoxStyle.DropDownList;
}
public override void OnDrawItem(DrawItemEventArgs e)
{
// Get the item.
var item = this.CmbBoxlineStyles.SelectedIndex.ToString();
if(item == null)
return;
int startX = e.Bounds.X;
int startY = (e.Bounds.Y + 1);
int endX = e.Bounds.X + 5;
int endY = (e.Bounds.Y + 1);
//Draw the lines
Pen pen = new Pen(Color.Blue);
e.Graphics.DrawLine(pen, new Point(startX, startY), new Point(endX, endY));
}
}
I am getting this error: Error 1 'Fdrc.frmDlgGraphOptions.OnDrawItem(System.Windows.Forms.DrawItemEventArgs)': no suitable method found to override
Thank you
Sun

The form doesn't have an OnDrawItem event, so there is nothing to override.
Instead, you need to use the DrawItem event of the combobox:
public frmDlgGraphOptions()
{
InitializeComponent();
CmbBoxlineStyles.DropDownStyle = ComboBoxStyle.DropDownList;
CmbBoxlineStyles.DrawMode = DrawMode.OwnerDrawFixed;
CmbBoxlineStyles.DrawItem += CmbBoxlineStyles_DrawItem;
}
void CmbBoxlineStyles_DrawItem(object sender, DrawItemEventArgs e) {
// draw
}
Make sure you set the DrawMode property so that the control knows to call your draw method.
If you are trying to make your own ComboBox control that draws those line items, I suspect this might be what you are looking for:
public class MyCombo : ComboBox {
public MyCombo() {
this.DropDownStyle = ComboBoxStyle.DropDownList;
this.DrawMode = DrawMode.OwnerDrawFixed;
}
protected override void OnDrawItem(DrawItemEventArgs e) {
if (e.Index > -1) {
int startX = e.Bounds.Left + 5;
int startY = (e.Bounds.Y + e.Bounds.Height / 2);
int endX = e.Bounds.Right - 5;
int endY = (e.Bounds.Y + e.Bounds.Height / 2);
using (Pen p = new Pen(Color.Blue, (Int32)this.Items[e.Index])) {
e.Graphics.DrawLine(p, new Point(startX, startY), new Point(endX, endY));
}
}
base.OnDrawItem(e);
}
}
Then you just add your pen size numbers when using the control:
MyCombo CmbBoxlineStyles = new MyCombo();
CmbBoxlineStyles.Items.Add(1);
CmbBoxlineStyles.Items.Add(2);
CmbBoxlineStyles.Items.Add(3);
CmbBoxlineStyles.Items.Add(4);
Resulting in:

The Form type doesn't have a OnDrawItem method hence there is nothing to override. In order to override the method you will need to inherit directly from the ComboBox type.

You are trying to override a method of the Form, but what you need to do is to change the behaviour of the ComboBox control. So either create a descendant ComboBox class and override the method there, or add a event handler to CmbBoxlineStyles.DrawItem event (this can be done using the designer) and implement your code there.

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);
}
}

how can I split a panel to clickable segments in c# winform?

I am trying to simulate a LED display board with c# . I need a control which contains 1536 clickable controls to simulate LEDs (96 in width and 16 in Height). I used a panel named pnlContainer for this and user will add 1536 tiny customized panels at runtime. These customized panels should change their color by click event at runtime. Everything works . But adding this number of tiny panels to the container takes long time ( about 10 secs). What is your suggestion to solve this issue? Any tips are appreciated.
this is my custome panel:
public partial class LedPanel : Panel
{
public LedPanel()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (this.BackColor == Color.Black)
{
this.BackColor = Color.Red;
}
else
{
this.BackColor = Color.Black;
}
}
}
}
and this is piece of code which adds tiny panels to the pnlContainer :
private void getPixels(Bitmap img2)
{
pnlContainer.Controls.Clear();
for (int i = 0; i < 96; i++)
{
for (int j = 0; j < 16; j++)
{
Custom_Controls.LedPanel led = new Custom_Controls.LedPanel();
led.Name = i.ToString() + j.ToString();
int lWidth = (int)(pnlContainer.Width / 96);
led.Left = i * lWidth;
led.Top = j * lWidth;
led.Width = led.Height = lWidth;
if (img2.GetPixel(i, j).R>numClear.Value)
{
led.BackColor = Color.Red;
}
else
{
led.BackColor = Color.Black;
}
led.BorderStyle = BorderStyle.FixedSingle;
pnlContainer.Controls.Add(led);
}
}
}
Is there any better approach or better control instead of panelto do this?
I agree with what #TaW recommends. Don't put 1000+ controls on a form. Use some sort of data structure, like an array to keep track of which LEDs need to be lit and then draw them in the Paint event of a Panel.
Here's an example. Put a Panel on a form and name it ledPanel. Then use code similar to the following. I just randomly set the values of the boolean array. You would need to set them appropriately in response to a click of the mouse. I didn't include that code, but basically you need to take the location of the mouse click, determine which array entry needs to be set (or unset) and then invalidate the panel so it will redraw itself.
public partial class Form1 : Form
{
//set these variables appropriately
int matrixWidth = 96;
int matrixHeight = 16;
//An array to hold which LEDs must be lit
bool[,] ledMatrix = null;
//Used to randomly populate the LED array
Random rnd = new Random();
public Form1()
{
InitializeComponent();
ledPanel.BackColor = Color.Black;
ledPanel.Resize += LedPanel_Resize;
//clear the array by initializing a new one
ledMatrix = new bool[matrixWidth, matrixHeight];
//Force the panel to repaint itself
ledPanel.Invalidate();
}
private void LedPanel_Resize(object sender, EventArgs e)
{
//If the panel resizes, then repaint.
ledPanel.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
//clear the array by initializing a new one
ledMatrix = new bool[matrixWidth, matrixHeight];
//Randomly set 250 of the 'LEDs';
for (int i = 0; i < 250; i++)
{
ledMatrix[rnd.Next(0, matrixWidth), rnd.Next(0, matrixHeight)] = true;
}
//Make the panel repaint itself
ledPanel.Invalidate();
}
private void ledPanel_Paint(object sender, PaintEventArgs e)
{
//Calculate the width and height of each LED based on the panel width
//and height and allowing for a line between each LED
int cellWidth = (ledPanel.Width - 1) / (matrixWidth + 1);
int cellHeight = (ledPanel.Height - 1) / (matrixHeight + 1);
//Loop through the boolean array and draw a filled rectangle
//for each one that is set to true
for (int i = 0; i < matrixWidth; i++)
{
for (int j = 0; j < matrixHeight; j++)
{
if (ledMatrix != null)
{
//I created a custom brush here for the 'off' LEDs because none
//of the built in colors were dark enough for me. I created it
//in a using block because custom brushes need to be disposed.
using (var b = new SolidBrush(Color.FromArgb(64, 0, 0)))
{
//Determine which brush to use depending on if the LED is lit
Brush ledBrush = ledMatrix[i, j] ? Brushes.Red : b;
//Calculate the top left corner of the rectangle to draw
var x = (i * (cellWidth + 1)) + 1;
var y = (j * (cellHeight + 1) + 1);
//Draw a filled rectangle
e.Graphics.FillRectangle(ledBrush, x, y, cellWidth, cellHeight);
}
}
}
}
}
private void ledPanel_MouseUp(object sender, MouseEventArgs e)
{
//Get the cell width and height
int cellWidth = (ledPanel.Width - 1) / (matrixWidth + 1);
int cellHeight = (ledPanel.Height - 1) / (matrixHeight + 1);
//Calculate which LED needs to be turned on or off
int x = e.Location.X / (cellWidth + 1);
int y = e.Location.Y / (cellHeight + 1);
//Toggle that LED. If it's off, then turn it on and if it's on,
//turn it off
ledMatrix[x, y] = !ledMatrix[x, y];
//Force the panel to update itself.
ledPanel.Invalidate();
}
}
I'm sure there can be many improvements to this code, but it should give you an idea on how to do it.
#Chris and #user10112654 are right.
here is a code similar to #Chris but isolates the displaying logic in a separate class. (#Chris answered your question when I was writing the code :))))
just create a 2D array to initialize the class and pass it to the Initialize method.
public class LedDisplayer
{
public LedDisplayer(Control control)
{
_control = control;
_control.MouseDown += MouseDown;
_control.Paint += Control_Paint;
// width and height of your tiny boxes
_width = 5;
_height = 5;
// margin between tiny boxes
_margin = 1;
}
private readonly Control _control;
private readonly int _width;
private readonly int _height;
private readonly int _margin;
private bool[,] _values;
// call this method first of all to initialize the Displayer
public void Initialize(bool[,] values)
{
_values = values;
_control.Invalidate();
}
private void MouseDown(object sender, MouseEventArgs e)
{
var firstIndex = e.X / OuterWidth();
var secondIndex = e.Y / OuterHeight();
_values[firstIndex, secondIndex] = !_values[firstIndex, secondIndex];
_control.Invalidate(); // you can use other overloads of Invalidate method for the blink problem
}
private void Control_Paint(object sender, PaintEventArgs e)
{
if (_values == null)
return;
e.Graphics.Clear(_control.BackColor);
for (int i = 0; i < _values.GetLength(0); i++)
for (int j = 0; j < _values.GetLength(1); j++)
Rectangle(i, j).Paint(e.Graphics);
}
private RectangleInfo Rectangle(int firstIndex, int secondIndex)
{
var x = firstIndex * OuterWidth();
var y = secondIndex * OuterHeight();
var rectangle = new Rectangle(x, y, _width, _height);
if (_values[firstIndex, secondIndex])
return new RectangleInfo(rectangle, Brushes.Red);
return new RectangleInfo(rectangle, Brushes.Black);
}
private int OuterWidth()
{
return _width + _margin;
}
private int OuterHeight()
{
return _height + _margin;
}
}
public class RectangleInfo
{
public RectangleInfo(Rectangle rectangle, Brush brush)
{
Rectangle = rectangle;
Brush = brush;
}
public Rectangle Rectangle { get; }
public Brush Brush { get; }
public void Paint(Graphics graphics)
{
graphics.FillRectangle(Brush, Rectangle);
}
}
this is how it's used in the form:
private void button2_Click(object sender, EventArgs e)
{
// define the displayer class
var displayer = new LedDisplayer(panel1);
// define the array to initilize the displayer
var display = new bool[,]
{
{true, false, false, true },
{false, true, false, false },
{false, false, true, false },
{true, false, false, false }
};
// and finally
displayer.Initialize(display);
}

Trying to add user control to form crashes Visual Studio

I created a UserControl called Switch.
I built my project and this UC successfully shown in my toolbox.
So I tried to add it to my frmMain by dragging it to the form. But at this moment, Visual Studio always shows an error message:
A new guard page for the stack cannot be created.
After clicking OK, devenv.exe crashes.
I have to mention, that I have another user control in the same namespace and folder as Switch. This UC works fine.
This is the code of my Switch user control:
public partial class Switch : UserControl
{
private Rectangle switchRectangle;
private int xOn = 0; // switchRectangle x position, when switch is on
private int xOff = 0; // switchRectangle x position, when switch is off
private Color SwitchColor = Color.Black;
private Color OuterRectangleColor = Color.DarkGray;
private Color InnerRectangleColor
{
get { return this.On ? Color.DodgerBlue : InnerRectangleColor; }
}
public bool On { get; set; }
public Switch()
{
InitializeComponent();
ReloadSwitchRectangle();
xOn = this.Width - switchRectangle.Width;
}
private void Switch_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(this.BackColor);
// Create inner rectangle, on every side by 2px smaller than ClientRectangle
const int amountToDecrease = 2;
Rectangle innerRectangle = new Rectangle(this.ClientRectangle.X + amountToDecrease, this.ClientRectangle.Y + amountToDecrease,
this.ClientRectangle.Width - amountToDecrease * 2, this.ClientRectangle.Height - amountToDecrease * 2);
ReloadSwitchRectangle();
e.Graphics.DrawRectangle(new Pen(OuterRectangleColor), this.ClientRectangle); // Draw outer rectangle
e.Graphics.FillRectangle(new SolidBrush(InnerRectangleColor), innerRectangle); // Fill inner rectangle
e.Graphics.FillRectangle(new SolidBrush(SwitchColor), switchRectangle);
}
private void ReloadSwitchRectangle()
{
int x = this.On ? xOn : xOff;
switchRectangle = new Rectangle(x, 0, this.Width / 5, this.Height);
}
}
This is the problem:
private Color InnerRectangleColor
{
get { return this.On ? Color.DodgerBlue : InnerRectangleColor; }
}
You have infinite recursion here when this.On is set to false.
Thanks to Sriram Sakthivel, I solved my problem like this:
private Color InnerRectangleColor
{
get { return this.On ? Color.DodgerBlue : OuterRectangleColor; }
}

Painting a TextBox

I'm in need of a way to make TextBox appear like a parallelogram but i can't figure out how to do so. I currently have this code:
private void IOBox_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Point cursor = PointToClient(Cursor.Position);
Point[] points = { cursor, new Point(cursor.X + 50, cursor.Y), new Point(cursor.X + 30, cursor.Y - 20),
new Point(cursor.X - 20, cursor.Y - 20) };
Pen pen = new Pen(SystemColors.MenuHighlight, 2);
g.DrawLines(pen, points);
}
But apparently it's not working. Either i misplaced/misused it or i'm not doing something right.
This is the method that i use to add it.
int IOCounter = 0;
private void inputOutput_Click(object sender, EventArgs e)
{
IOBox box = new IOBox();
box.Name = "IOBox" + IOCounter;
IOCounter++;
box.Location = PointToClient(Cursor.Position);
this.Controls.Add(box);
}
Any idea how i can fix it? IOBox is a UserControl made by me which contains a TextBox. Is that rightful to do?
If its possible, you should make your application using WPF. WPF is designed to do exactly what you are trying to do.
However, it can be done in WinForms, though not easily. You will need to make a new class that inherits the TextBox WinForm control. Here is an example that makes a TextBox look like a circle:
public class MyTextBox : TextBox
{
public MyTextBox() : base()
{
SetStyle(ControlStyles.UserPaint, true);
Multiline = true;
Width = 130;
Height = 119;
}
public override sealed bool Multiline
{
get { return base.Multiline; }
set { base.Multiline = value; }
}
protected override void OnPaintBackground(PaintEventArgs e)
{
var buttonPath = new System.Drawing.Drawing2D.GraphicsPath();
var newRectangle = ClientRectangle;
newRectangle.Inflate(-10, -10);
e.Graphics.DrawEllipse(System.Drawing.Pens.Black, newRectangle);
newRectangle.Inflate(1, 1);
buttonPath.AddEllipse(newRectangle);
Region = new System.Drawing.Region(buttonPath);
base.OnPaintBackground(e);
}
}
Keep in mind that you will still have to do other things, such as clipping the text, etc. But this should get you started.

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;
}
}
}

Categories