Allow the custom control to scroll properly

I have a custom control consisting of a filled rounded rectangle with text. (Actual control is more complicated, but the code shown here has the sames symptoms.) I linked the control instances to a panel and made this group a child of another panel with AutoScroll = true. I thought this would be enough for the scroll behavior to work correctly, but if you scroll so that the left side of my control should go off the left side of the panel, it sticks and the control shrinks. The same thing happens when scrolling so that the control should go away at the top. (Below and to the right, it doesn't seem to be a problem). Here is sample code (link to System.Windows.Forms and System.Drawing required.) I am using Visual Studio 2010 with the .NET 4 client on Windows.

using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace CustomControlScrollTest { static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } // Form with a Dock = Fill panel with autoscroll turned on, and a nested panel // with few custom roundRectControls. public class Form1 : Form { Panel autoScrollPanel; Panel rectPanel; public Form1() { Size = new Size(300, 200); autoScrollPanel = new Panel(); autoScrollPanel.Dock = DockStyle.Fill; autoScrollPanel.AutoScroll = true; autoScrollPanel.AutoScrollMinSize = new Size(600, 450); autoScrollPanel.Resize += autoScrollPanel_Resize; autoScrollPanel.Scroll += autoScrollPanel_Scroll; Controls.Add(autoScrollPanel); rectPanel = new Panel(); rectPanel.Size = autoScrollPanel.AutoScrollMinSize; rectPanel.Controls.AddRange(new RoundRectControl[] { new RoundRectControl(), new RoundRectControl(), new RoundRectControl(), new RoundRectControl(), new RoundRectControl() }); foreach (Control c in rectPanel.Controls) { c.Click += c_Click; } autoScrollPanel.Controls.Add(rectPanel); placeBoxes(); } // we want to be able to recalculate the boxes position at any time // in the real program this occurs due to model changes void c_Click(object sender, EventArgs e) { placeBoxes(); } void autoScrollPanel_Scroll(object sender, ScrollEventArgs e) { Refresh(); } void autoScrollPanel_Resize(object sender, EventArgs e) { Refresh(); } private void placeBoxes() { for (int i = 0; i < rectPanel.Controls.Count; ++i) { int j = i + 1; var node = rectPanel.Controls[i] as RoundRectControl; if (node != null) { node.Title = "Hello (" + j + ")"; node.Location = new Point(i * 100, j * 75); node.Visible = true; } } } } // A rounded rectangle filled blue with a black border and white text // the size is determined by the text public class RoundRectControl : Control { public RoundRectControl() { var f = SystemFonts.MessageBoxFont; titleFont = new Font(f.Name, f.SizeInPoints + 2, FontStyle.Bold, GraphicsUnit.Point); ResizeRedraw = true; } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); var g = e.Graphics; var left = e.ClipRectangle.X; var right = left + titleWidth + 2 * radius; var top = e.ClipRectangle.Y; var bottom = top + nodeHeight + 2 * radius; var r2 = 2 * radius; using (var path = new GraphicsPath()) { path.AddArc(left, bottom - r2, r2, r2, 90, 90); path.AddArc(left, top, r2, r2, 180, 90); path.AddArc(right - r2, top, r2, r2, 270, 90); path.AddArc(right - r2, bottom - r2, r2, r2, 0, 90); path.CloseFigure(); g.FillPath(titleBrush, path); g.DrawPath(borderPen, path); } g.DrawString(title, titleFont, titleTextBrush, left + radius, top + radius); } private string title; public string Title { get { return title; } set { title = value; Size = getSize(); Invalidate(); } } private Brush titleBrush = Brushes.Blue; private Brush titleTextBrush = Brushes.White; private Pen borderPen = Pens.Black; private Size getSize() { var g = CreateGraphics(); var titleSize = g.MeasureString(title, titleFont); titleWidth = (int)titleSize.Width; nodeHeight = (int)titleSize.Height; return new Size(titleWidth + 2 * radius + 1, nodeHeight + 2 * radius + 1); } public override Size GetPreferredSize(Size proposedSize) { return getSize(); } private int titleWidth; private int nodeHeight; private Font titleFont; private int radius = 5; } } 
+5
source share
1 answer

try it

 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); var g = e.Graphics; //total width and height of rounded rectangle var width = titleWidth + 2 * radius + 1; var height = nodeHeight + 2 * radius + 1; var left = e.ClipRectangle.X; var top = e.ClipRectangle.Y; //check if clipping occurs. If yes, set to 0 if (width > e.ClipRectangle.Width) { left = 0; // *= -1; } //check if clipping occurs.If yes, set to 0 if (height > e.ClipRectangle.Height) { top = 0; // *= -1 } var right = left + titleWidth + 2 * radius; var bottom = top + nodeHeight + 2 * radius; var r2 = 2 * radius; using (var path = new GraphicsPath()) { path.AddArc(left, bottom - r2, r2, r2, 90, 90); path.AddArc(left, top, r2, r2, 180, 90); path.AddArc(right - r2, top, r2, r2, 270, 90); path.AddArc(right - r2, bottom - r2, r2, r2, 0, 90); path.CloseFigure(); g.FillPath(titleBrush, path); g.DrawPath(borderPen, path); } g.DrawString(title, titleFont, titleTextBrush, left + radius, top + radius); } 

The problem is that you are scrolling to the right (the lines are moving to the left, e.ClipRectangle.Width is getting smaller), and the rectangle is out of scope, e.ClipRectangle.X is positive! Therefore, in this case, we set it to zero. e.ClipRectangle.X cannot be negative, so even if you invert it (solution before editing), it becomes zero.

EDIT

You can just do

 var left = 0; var right = left + titleWidth + 2 * radius; var top = 0; var bottom = top + nodeHeight + 2 * radius; 

Both work

You can use these styles

 this.SetStyle(ControlStyles.DoubleBuffer, true); this.SetStyle(ControlStyles.UserPaint, true); this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); 

to avoid flickering

Walter

+6
source

Source: https://habr.com/ru/post/1203176/


All Articles