How to add scrolling to Panel - c#

Basically, I've created an extension of the panel class that adds draws multiple bitmaps onto itself In order to create multiple musical staves. I've tried adding a vertical scroll bar onto the panel but that hasn't worked. My Paint procedure is similar to this
private void StavePanel_Paint(object sender, PaintEventArgs e)
{
for(int i = 0; i < linenumber; i++)
{
Bitmap bmp = new Bitmap(Width, 200);
//edit bmp to resemble stave
e.Graphics.DrawImage(bmp,new Point(0,200*i);
}
}

Just set the AutoScrollMinSize property:
panel1.AutoScrollMinSize = new Size(0, 1000);
During the paint event, you need to translate the positions of your drawing by using the TranslateTransform method. Also, you need to dispose your bitmaps after you draw them:
e.Graphics.TranslateTransform(panel1.AutoScrollPosition.X, panel1.AutoScrollPosition.Y);
using (Bitmap bmp = new Bitmap(Width, 200)) {
//edit bmp to resemble stave
e.Graphics.DrawImage(bmp,new Point(0,200*i);
}
or create and store them ahead of time to avoid that cost during the paint event.

Set the AutoScroll property to true.
You might also consider alternatives:
FlowLayoutPanel and add PictureBoxes dynamically instead of painting.
TableLayoutPanel and add PictureBoxes dynamically instead of painting.
extend ListBox and set the DrawMode property to OwnerDrawFixed or OwnerDrawVariable and then override the methods OnPaint and OnMeasureItem (only for OwnerDrawVariable).

If you want to continue using your existing pattern of calling GDI code to paint your control you should add a scrollbar control and add an event handler to its change event. The change handler doesn't need to do anything other than call .Invalidate on the panel. .Invalidate is a signal to the control that it is "dirty" and needs to be redrawn. You will need to modify your painting code to offset the drawing in the inverse direction of the scrollbar value.
So if your scrollbar is at position 50, you should draw everything at Y - 50.
If you are using pure GDI drawing code there is no need to mess with the AutoScroll property at all. That is only used if your panel hosts an actual control that is larger than the panel.

As others mentioned, you need to set AutoScroll to true. But then, anytime you add or remove a bitmap (or at the beginning if they are fixed), you need to set the AutoScrollMinSize height using the formula bitmapCount * bitmapHeight. Also in your paint handler you need to consider the AutoScrollPosition.Y property.
Here is a small example of the concept in action:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Tests
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var panel = new Panel { Dock = DockStyle.Fill, Parent = form };
// Setting the AutoScrollMinSize
int bitmapCount = 10;
int bitmapHeight = 200;
panel.AutoScrollMinSize = new Size(0, bitmapCount * bitmapHeight);
panel.Paint += (sender, e) =>
{
// Considering the AutoScrollPosition.Y
int offsetY = panel.AutoScrollPosition.Y;
var state = offsetY != 0 ? e.Graphics.Save() : null;
if (offsetY != 0) e.Graphics.TranslateTransform(0, offsetY);
var rect = new Rectangle(0, 0, panel.ClientSize.Width, bitmapHeight);
var sf = new StringFormat(StringFormat.GenericTypographic) { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
for (int i = 0; i < bitmapCount; i++)
{
// Your bitmap drawing goes here
e.Graphics.FillRectangle(Brushes.Yellow, rect);
e.Graphics.DrawRectangle(Pens.Red, rect);
e.Graphics.DrawString("Bitmap #" + (i + 1), panel.Font, Brushes.Blue, rect, sf);
rect.Y += bitmapHeight;
}
if (state != null) e.Graphics.Restore(state);
};
Application.Run(form);
}
}
}
EDIT: As LarsTech correctly mentioned in the comments, you don't really need to set AutoScroll property in this case. All other remains the same.

Related

How to repaint certain part of panel / or use two panels on top of each other with the top one having transparent background?

I have a C# WinForms project where I have to paint some things on my panel. I load in a grid from a file that translates to squares that are painted on the panel. Then I load in a file with dots that are then painted on top of the squares.
I then have a function that moves the dots around. But the repaint function is called every tick which causes the whole grid to flicker continously because it is painted so quick after each other.
How do I make it so that only the dots are repainted?
The repaint and paint functions are as follows:
private void Repaint(object sender, EventArgs e)
{
GridPanel.Invalidate();
}
private void GridPanel_Paint(object sender, PaintEventArgs e)
{
if ((GridPanel.Width != grid.Width || GridPanel.Height != grid.Height))
{
grid.Height = GridPanel.Height;
grid.Width = GridPanel.Width;
grid.setDimensions();
}
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.White), new Rectangle(0, 0, grid.Width, grid.Height));
foreach(Square square in grid.Squares)
{
if (square.Letter != '_')
{
SolidBrush color = new SolidBrush(square.Color);
g.FillRectangle(color, new Rectangle(square.X * grid.squareWidth, square.Y * grid.squareHeight, grid.squareWidth, grid.squareHeight));
}
}
foreach(Artist artist in grid.Artists)
{
SolidBrush color = new SolidBrush(artist.Color);
g.FillRectangle(color, new Rectangle(Convert.ToInt32(artist.X * grid.squareWidth), Convert.ToInt32(artist.Y * grid.squareHeight), grid.artistWidth, grid.artistHeight));
}
}
I also tried to use a second panel for the dots so I only have to repaint that one. But I cant get a transparent background working on the second panel, so the first panel is not visible this way.
Somebody knows a good solution for this problem?

How to implement a vertical and horizontal scroll bar in WinForms when using paint?

In my code I draw a rectangle and usually the rectangle is too large for the screen, even when maximised. I have set the form property AutoScroll to true and this doesn't seem to do anything. There won't be anything else on my form except the rectangle painting, how can I implement a vertical and horizontal scroll?
PrintingDesignForm form = new PrintingDesignForm();
form.Paint += (se, pe) => {
var r = new Rectangle(parameters.RectangleXPosition, parameters.RectangleYPosition, (int)Math.Ceiling(parameters.RectangleWidth) * 72, (int)Math.Ceiling(parameters.RectangleLength) * 72);
var brush = new SolidBrush(Color.FromArgb(255, 255, 204));
pe.Graphics.FillRectangle(brush, r);
using (var pen = new Pen(brush.Color, 2))
pe.Graphics.DrawRectangle(pen, r);
};
form.WindowState = FormWindowState.Maximized;
form.Show();
Setting AutoScroll = true on a Control/Form alone will only ensure that all Controls you add/nest to the parent will either show or can be reached by the scrollbars which will show up as needed.
This doesn't do anything for stuff you draw.
To make the drawing scrollable you need to set the AutoScrollMinSize to large enough values.
If you don't know in advance then at least while drawing you should be able to determine them from your data.
In Form Properties Set
AutoScroll = True

Custom ListView control will not paint when first shown

I have created a custom ListView control to suit my needs and I'm having an issue that causes the ListView not to show any contents (not drawing anything, just white) when the form first loads.
If I resize the form or click on my control (anything that forces a repaint on the ListView) then it shows up as expected.
As a side note, it used to work just fine until I made a small change today and rebuilt the control. I removed all the changes I made and rebuilt again but the issue still happens. Any ideas as to why it will not show up (paint) when the form is first loaded?
This is what I use to do the custom drawing on my custom ListView control...
protected override void OnDrawItem(DrawListViewItemEventArgs e)
{
Image image = e.Item.ImageList.Images[e.Item.ImageIndex];
Size textSize = new Size((int)e.Graphics.MeasureString(e.Item.Text, e.Item.Font).Width, (int)e.Graphics.MeasureString(e.Item.Text, e.Item.Font).Height);
//Get the area of the item to be painted
Rectangle bounds = e.Bounds;
bounds.X = 0;
bounds.Width = this.Width;
//Set the spacing on the list view items
int hPadding = 0;
int vPadding = 0;
IntPtr padding = (IntPtr)(int)(((ushort)(hPadding + bounds.Width)) | (uint)((vPadding + bounds.Height) << 16));
SendMessage(this.Handle, (uint)ListViewMessage.LVM_SETICONSPACING, IntPtr.Zero, padding);
//Set the positions of the image and text
int imageLeft = (bounds.Width / 2) - (image.Width / 2);
int imageTop = bounds.Top + 3;
int textLeft = (bounds.Width / 2) - (textSize.Width / 2);
int textTop = imageTop + image.Height;
Point imagePosition = new Point(imageLeft, imageTop);
Point textPosition = new Point(textLeft, textTop);
//Draw background
using (Brush brush = new SolidBrush(e.Item.BackColor))
e.Graphics.FillRectangle(brush, bounds);
//Draw selected
if (e.Item.Selected)
{
using (Brush brush = new SolidBrush(m_SelectedColor))
e.Graphics.FillRectangle(brush, bounds);
}
//Draw image
e.Graphics.DrawImage(image, imagePosition);
//Draw text
e.Graphics.DrawString(e.Item.Text, e.Item.Font, new SolidBrush(e.Item.ForeColor), textPosition);
}
I also set the following things in the Constructor of my custom control...
public MyListView()
{
this.DoubleBuffered = true;
this.OwnerDraw = true;
this.View = View.LargeIcon;
this.Cursor = Cursors.Hand;
this.Scrollable = false;
}
I also inherit the ListView class...
public class MyListView : ListView
{
//All my source
}
You need to set the set the control to redraw itself when resized. So add this code in constructor of your control:
this.ResizeRedraw = true;
My apologies:
Doing the below reset my event handlers and the problem went away. However, once I hooked them up, I found the Resize event handler that I used to set the ColumnWidth was causing the issue. Why setting the ColumnWidth is causing this, I do not know.
Arvo Bowen's comment on this answer fixed it for me also (.NET 4.8 Framework, VS2022). To be clear, not requiring this.ResizeRedraw = true; from the answer.
So after much headache and time I found that I had absolutely nothing wrong with my control. It was your answer that made me create another control just like my existing one and test it. To my surprise it worked great. I simply copied my existing non-working control and pasted it on the form and then new one worked! Sometimes VS just does weird things... Somehow I managed to muck up the control's creation object or something..

Children inherit parents appearance

I have created a simple custom panel using ContainerControl as my base. I've added custom properties to create borders and gradient backgrounds. If I override OnPaint and OnPaintBackground all child controls of the parent inherit the gradient and border styles. As a work around I have used the parents BackgroundImage property which works fine but has a few random quirks. There has to be a better way of approaching this issue but I have found no solution. Are there any Window API functions via Interop or other C# methods to fix this? If so please provide an example.
EDIT! Here is the style being copied (ugly example but makes the point):
EDIT 2! Here is a simple hard-coded ContainerControl without all the properties, designer attributes, etc.
public class Container : ContainerControl
{
protected override void OnPaintBackground( PaintEventArgs e )
{
using ( var brush = new LinearGradientBrush( e.ClipRectangle, Color.Red, Color.Blue, LinearGradientMode.Vertical ) )
{
e.Graphics.FillRectangle( brush, e.ClipRectangle );
}
}
}
If a Label control is created with its BackColor property set to Color.Transparent, it will end up calling its parent's OnPaintBackground() implementation.
If you modify Jon's example like this:
var label = new Label {
Text = "Label",
Location = new Point(20, 50),
BackColor = Color.Transparent
};
Then you will reproduce the issue.
There is an easy workaround, however. The problem comes from the way you're creating the linear gradient brush. Since you're passing e.ClipRectangle to its constructor, the shape of the gradient will vary depending on the control being rendered (container or label). On the other hand, if you pass the ClientRectangle of the container, then the gradient will always have the same shape and the result should be what you're looking for:
protected override void OnPaintBackground(PaintEventArgs e)
{
using (var brush = new LinearGradientBrush(ClientRectangle,
Color.Red, Color.Blue, LinearGradientMode.Vertical)) {
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
The result is:
Initialize the properties on control create/load
Then "INVALIDATE" the control to force a redraw of the control
I can't reproduce this simply on my Windows 7 machine - which suggests it may be one of the properties you've set in the designer. Short but complete program:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class GradientContainer : ContainerControl
{
protected override void OnPaintBackground(PaintEventArgs e)
{
using (var brush = new LinearGradientBrush(e.ClipRectangle,
Color.Red, Color.Blue, LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
}
class Test
{
static void Main()
{
var label = new Label {
Text = "Label",
Location = new Point(20, 50)
};
var container = new GradientContainer {
Size = new Size(200, 200),
Location = new Point(0, 0),
Controls = { label }
};
Form form = new Form {
Controls = { container },
Size = new Size(300, 300)
};
Application.Run(form);
}
}
And the result:

Drawn rectangle appears only on last instance of my userControl

I have a userControl which has some programmatically drawn rectangles. I need few instances of that userControl on my form (see the image). The problem is that only the last instance will show the drawn shapes!
I guess it has something to do with drawing surface or the Paint event handler
In case it might help, here's some of the code I use in my control:
private void MyUserControl_Paint(object sender, PaintEventArgs e)
{
showHoraireMaitresse();
Rectangle rec = showDisponibilités();
var b = new SolidBrush(Color.FromArgb(150, Color.Blue));
e.Graphics.FillRectangle (b, rec);
showOccupation();
}
private void showHoraireMaitresse()
{
heureDebut = 8;
for (int i = 0; i < 14; i++)
{
//Label d'heure -> This shows just fine
addLabel(i, heureDebut);
//Rectangles d'heure -> This shows only in last instance
var rectangle = new Rectangle(180 + i * largeurDUneHeure, 14, largeurDUneHeure, 30);
surface.DrawRectangle(defaultPen, rectangle);
}
addLabel(14, heureDebut);
}
Thank you!
Without further information, I'm going to guess that 'surface' is static.
Trace through OnPaint and check which control is painting, and what the bounds are for 'surface'. Perhaps all the controls are painting the same exact rectangle.

Categories

Resources