How does OnPaint work with a ScrollableControl? [duplicate] - c#

I draw my contents on a form inside OnPaint event with e.graphics.DrawLine(), etc... . So far I was drawing according to form size (resizing my elements) but now I'd like draw as big as I want, and if I draw outside the visible area (the place where object will be drawn is decided at runtime dynamically), I want user to use scroll bars in order to see parts of whole content which I draw.
I have enabled AutoScrolling but I don't know how it may help me when I don't have any controls on that form.
How can I do it?

Simply set the AutoScrollMinSize property to the size you want. The scrollbar(s) automatically appear when the form's ClientSize is smaller than this value. You'll also need to offset what you draw according to the scroll position, like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
this.AutoScroll = true;
this.AutoScrollMinSize = new Size(3000, 1000);
this.ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
e.Graphics.DrawLine(Pens.Black, 0, 0, 3000, 1000);
base.OnPaint(e);
}
}

First you should set AutoScroll = true; of that Form where you're drawing ,than the best way is to draw things into a Panel and Re-size the Panel to fit the Content drawled inside ,than the Form will Automatically Show it's Scroll Bar's.

Related

How to change control's size and add new control while form is minimized

I have a windows form that has 2 panels inside it, one takes up left half of the form, one right. Now when a certain event occurs in my program (I receive a message from server for example), I want to add a new control (say a third panel) between the two existing, so I need to make make them smaller and move them to the sides of my form.
This can happen while the form is minimized and this is where my problem is. Panels Size returns [0,0] when the form is minimized so I cant use it for calculations.
So my first question is, how can I get "original" size of controls while the form is minimized?
And then, even if I somehow calculated the new Size (say I have 400px wide form with 2x 200px panels and I want the new 3rd panel to be 200px wide, so the old panels will become 100px wide), and applied it:
leftPanel.Size = new Size(100, 100);
then after the form is restored from minimized state to normal state, the panel will be way bigger than specified 100x100. Seems like it will restore to the forms ClientSize + the newly specified size
Therefore my question: how can I add and resize controls to form while the form is minimized?
Sample procedures to resize Panels hosted in a Form and adapt the Layout when a new Panel is inserted in the middle of the two existing.
The WindowState of the hosting Form Forms is not relevant (it can be minimized, maximized or in normal state).
► Using the first method, if the Form is maximized, the Panels will retain the initial Height.
► Using the second method, as it is now, the Panels' Height will be set to the Form's ClientSize.Height. It can of course be changed, setting the TableLayoutPanel Row(s) to an Absolute height instead of AutoSize.
Using the Docking feature alone:
Set the Form AutoSizeMode = Dpi
Add two Panels to the Form (e.g., panelLeft and panelRight)
Set the Width of both Panels to 200
Set panelLeft to Dock = DockStyle.Left and panelRight to Dock = DockStyle.Right
Right-click the Panel on the left and select SendToBack (!important)
Adjust the Form Size: it should be: (418, 138). Not important, just for a visual confirmation
In the Form constructor set this.ClientSize = new Size(400, 100);
Add a new public method to the Form:
public void AdjustPanelsWidth(int newWidth)
{
this.panelLeft.Width = newWidth;
this.panelRight.Width = newWidth;
}
When you need to add a new Panel in the middle of the two existing Panels:
(someForm represents the current instance of the minimized Form)
int newSize = 100;
someForm.AdjustPanelsWidth(newSize);
var p = new Panel() {
Size = new Size(newSize * 2, 100),
Dock = DockStyle.Fill
};
p.BringToFront();
someForm.Controls.Add(p);
Using a TableLayoutPanel:
Add a TableLayoutPanel to the Form
Set it to Dock = DockStyle.Top
Edit Columns and Rows to have 3 Columns and 1 Row
Set the Columns Styles in the TLP Designer as:
Columns:
(0) Percent 50%
(1) AutoSize
(2) Percent 50%
Row:
(0) AutoSize
Closing the TLP Designer, it should appear to have just two Columns: since the central one is auto-sized and it has no content, its Width is currently 0.
Add two Panels to the Form (not to the TableLayoutPanel directly)
Set the Size of the Panels = (200, 100)
Drag one Panel inside the left Column of the TLP and the other to the Column on the right
! Verify, in VS Property panel, that the Column property of Panel on the Left is Column 0
The same for the Panel on the Right: the Colum property must be Column 2
If the Column is wrong, edit it manually.
Select both Panels and set both to Dock = DockStyle.Fill
(now you should see the TLP completely filled by the Panels, both occupying 50% of the TLP Size)
Adjust the Form size as before (still not actually important)
In the Form constructor set this.ClientSize = new Size(400, 100); (as before)
Add a public method to the Form:
public void AddControl(Control control)
{
// Add a Control to Column 1 - Row 0
this.tableLayoutPanel1.Controls.Add(control, 1, 0);
panel.Dock = DockStyle.Fill;
}
To add a new Panel in the middle Column:
var p = new Panel() {
Size = new Size(200, 100),
BackColor = Color.Red,
Margin = new Padding(0)
};
someForm.AddControl(p);
Structure of a Form that implements the TableLAyoutPanel method described:
ClockMinimize() => Minimizes the Clock size, squeezing it between two other Panels
ClockShow() => Enlarges the Clock to overlap the other Panels, which will resize to completely fill the Form's ClientArea:
using System.Drawing;
using System.Windows.Forms;
public partial class frmClock : Form
{
public frmClock() => InitializeComponent();
private int m_ClientHeight = 0;
public void ClockShow()
{
this.panClock.Parent = this;
this.panClock.Size = new Size(360, 80);
this.panClock.Location = new Point(20, 10);
// Adjust the Clock Font Size here
this.panClock.BringToFront();
}
public void ClockMinimize()
{
this.panClock.Size = new Size(200, 40);
tableLayoutPanel1.Controls.Add(this.panClock, 1, 0);
this.panClock.Margin = new Padding(0, (m_ClientHeight - this.panClock.Height) / 2, 0, 0);
// Adjust the Clock Font Size here
AdjustPanelsWidth(panClock.Width / 2);
}
public void AdjustPanelsWidth(int newWidth)
{
this.panLeft.Width = newWidth;
this.panRight.Width = newWidth;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.MinimumSize = this.Size;
m_ClientHeight = this.ClientSize.Height;
}
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
if (this.ClientSize.Height > 0) {
m_ClientHeight = this.ClientSize.Height;
}
}
}
I have come up with this workaround: I'm postponing resizing/moving until the form returns from minimized state, using async/await.
Instead of my original function:
public void changeControlPositionAndSize() {
//calculate new size and location based on size and location of neighboring controls
myPanel.Location = ...
myPanel.Size = ...
}
I'm now using:
public async Task changeControlPositionAndSize()
{
while (WindowState == FormWindowState.Minimized)
{
await Task.Delay(2000);
}
//calculate new size and location based on size and location of neighboring controls
myPanel.Location = ...
myPanel.Size = ...
}

How to create transparent controls in Windows Forms when controls are layered

I am trying to implement a "Fillable Form" in which editable text fields appear over top of an image of a pre-preprinted form for a dot matrix printer. (using c# and Windows Forms and targeting .Net 2.0) My first idea was to use the image as the Windows Form background, but it looked horrible when scrolling and also did not scroll properly with the content.
My next attempt was to create a fixed-size window with a panel that overflows the bounds of the window (for scrolling purposes.) I added a PictureBox to the panel, and added my textboxes on top of it. This works fine, except that TextBoxes do not support transparency, so I tried several methods to make the TextBoxes transparent. One approach was to use an odd background color and a transparency key. Another, described in the following links, was to create a derived class that allows transparency:
Transparency for windows forms textbox
TextBox with a Transparent Background
Neither method works, because as I have come to find out, "transparency" in Windows Forms just means that the background of the window is painted onto the control background. Since the PictureBox is positioned between the Window background and the TextBox, it gives the appearance that the TextBox is not transparent, but simply has a background color equal to the background color of the Window. With the transparency key approach, the entire application becomes transparent so that you can see Visual Studio in the background, which is not what I want. So now I am trying to implement a class that derives from TextBox and overrides either OnPaint or OnPaintBackground to paint the appropriate part of the PictureBox image onto the control background to give the illusion of transparency as described in the following link:
How to create a transparent control which works when on top of other controls?
First of all, I can't get it working (I have tried various things, and either get a completely black control, or just a standard label background), and second of all, I get intermittent ArgumentExceptions from the DrawToBitmap method that have the cryptic message "Additional information: targetBounds." Based on the following link from MSDN, I believe that this is because the bitmap is too large - in either event it seems inefficient to capture the whole form image here because I really just want a tiny piece of it.
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.drawtobitmap(v=vs.100).aspx
Here is my latest attempt. Can somebody please help me with the OnPaintBackground implementation or suggest a different approach? Thanks in advance!
public partial class TransparentTextbox : TextBox
{
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e); // not sure whether I need this
if (Parent != null)
{
foreach (Control c in Parent.Controls)
{
if (c.GetType() == typeof(PictureBox))
{
PictureBox formImg = (PictureBox)c;
Bitmap bitmap = new Bitmap(formImg.Width, formImg.Height);
formImg.DrawToBitmap(bitmap, formImg.Bounds);
e.Graphics.DrawImage(bitmap, -Left, -Top);
break;
}
}
Debug.WriteLine(Name + " didn't find the PictureBox.");
}
}
}
NOTE: This has been tagged as a duplicate, but I referenced the "duplicate question" in my original post, and explained why it was not working. That solution only works if the TextBox sits directly over the Window - if another control (such as my Panel and PictureBox) sit between the window and the TextBox, then .Net draws the Window background onto the TextBox background, effectively making its background look gray, not transparent.
I think I have finally gotten to the bottom of this. I added a Bitmap variable to my class, and when I instantiate the textboxes, I am setting it to contain just the portion of the form image that sits behind the control. Then I overload OnPaintBackground to display the Bitmap, and I overload OnPaint to manually draw the text string. Here is the updated version of my TransparentTextbox class:
public partial class TransparentTextbox : TextBox
{
public Bitmap BgBitmap { get; set; }
public TransparentTextbox()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(this.Text, this.Font, Brushes.Black, new PointF(0.0F, 0.0F));
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.DrawImage(BgBitmap, 0, 0);
}
}
... and here is the relevant part of how I instantiate:
Bitmap bgImage = (Bitmap)Bitmap.FromStream(Document.FormImage);
PictureBox pb = new PictureBox();
pb.Image = bgImage;
pb.Size = pb.Image.Size;
pb.Top = 0;
pb.Left = 0;
panel1.Controls.Add(pb);
foreach (FormField field in Document.FormFields)
{
TransparentTextbox tb = new TransparentTextbox();
tb.Width = (int)Math.Ceiling(field.MaxLineWidth * 96.0);
tb.Height = 22;
tb.Font = new Font("Courier", 12);
tb.BorderStyle = BorderStyle.None;
tb.Text = "Super Neat!";
tb.TextChanged += tb_TextChanged;
tb.Left = (int)Math.Ceiling(field.XValue * 96.0);
tb.Top = (int)Math.Ceiling(field.YValue * 96.0);
tb.Visible = true;
Bitmap b = new Bitmap(tb.Width, tb.Height);
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(bgImage, new Rectangle(0, 0, b.Width, b.Height), tb.Bounds, GraphicsUnit.Pixel);
tb.BgBitmap = b;
}
panel1.Controls.Add(tb);
}
I still need to work on how the text looks when I highlight it, and other things like that, but I feel like I am on the right track. +1 to Reza Aghaei and Mangist for commenting with other viable solutions!

How can I display a rectangle that overlaps everything in a panel control?

I want to display a rectangle drawing over EVERYTHING inside of a panel control. I only have a WebBrowser in the panel control as of now. If I start run the program WITHOUT adding the WebBrowser to the panel control, then I will be able to see the rectangle. But, if I add the WebBrowser to the panel control, then it will overlap my rectangle drawing.
How can I create a rectangle drawing INSIDE of a panel control, that will overlap everything? (always on top)
The code below will draw a blue rectangle inside of the panel control. But it will not be shown, because the panel contains a WebBrowser.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Graphics_Overlay
{
public partial class GraphicsOverlayForm : Form
{
public GraphicsOverlayForm()
{
InitializeComponent();
Panel panel = new Panel();
panel.Dock = DockStyle.Fill;
panel.Paint += panel_Paint;
WebBrowser webBrowser = new WebBrowser();
webBrowser.Dock = DockStyle.Fill;
webBrowser.Url = new Uri("http://stackoverflow.com/");
Controls.Add(panel);
// If I comment out the line below then I will
// be able to see my blue rectangle.
panel.Controls.Add(webBrowser);
}
private void panel_Paint(object sender, PaintEventArgs e)
{
ShowLineJoin(e);
}
private void ShowLineJoin(PaintEventArgs e)
{
// Create a new pen.
Pen skyBluePen = new Pen(Brushes.Blue);
// Set the pen's width.
skyBluePen.Width = 1;
// Set the LineJoin property.
skyBluePen.LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel;
// Draw a rectangle.
e.Graphics.DrawRectangle(skyBluePen,
new Rectangle(0, 0, 200, 200));
//Dispose of the pen.
skyBluePen.Dispose();
}
}
}
This is not possible. If you add the WebBrowser as a child control of the panel, it will be displayed on top of the Panel's background area. The Panel's background area is, of course, where you are drawing the blue rectangle. Your drawing is still there, the WebBrowser control is just being displayed on top of it.
There are three possible solutions that come to mind, listed here in no particular order:
Set the Visible property of the WebBrowser control to false whenever you want the blue rectangle (or whatever is drawn on the background of the Panel control to be visible). Set WebBrowser.Visible to true again so that the WebBrowser control can be used.
Rather than adding the WebBrowser control as a child control of the Panel, which is in turn a child control of the container form, add both the Panel and WebBrowser control as children of your container form. In other words, flatten the hierarchy. Because this removes the parent-child relationship, you can now alter the Z order of the controls, changing which one is visible. This is trivial in WinForms using the SendToBack and BringToFront member functions.
Create a new control, named something like BlueRectangle or DrawingSurface or whatever, that inherits from System.Windows.Forms.Control. Override its OnPaint event and put your drawing code there. Add an instance of this control class to your Panel control, as a child. Effectively, then, your Panel control will have two children: a BlueRectangle control and a WebBrowser control. Shuffle back and forth between the two by changing the Z order, as described above, with SendToBack and BringToFront.
On an unrelated note, you should prefer the using statement as a way of ensuring that an object implementing IDisposable is automatically disposed, rather than explicitly calling the Dispose method. For example, write your ShowLineJoin method like this:
private void ShowLineJoin(PaintEventArgs e)
{
// Create a new pen with the specified color and width.
using (Pen skyBluePen = new Pen(Brushes.Blue, 1))
{
// Set the LineJoin property.
skyBluePen.LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel;
// Draw a rectangle.
e.Graphics.DrawRectangle(skyBluePen,
new Rectangle(0, 0, 200, 200));
}
}
Simpler, easier to read, and safer.

Problems with Opacity

I'm trying to draw a semi-transparent background and then opaque elements on top of it.
How come I can't do something like this?
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
this.Opacity = 0.5;
pe.Graphics.FillRectangle(trans_black_brush, square_rect_big);
this.Opacity = 1;
pe.Graphics.FillRectangle(solid_red_brush, square_rect);
}
I'd appreciate if someone with better understanding of Form drawing could tell me why this doesn't work :)
Update:
The solution has 3 forms:
1) Main (program, buttons etc)
2) Semi-transparent background (screen size, using opacity)
3) Transparent background but solid brushes on top.
In Form2's constructor, I have this:
Foreground = new FormForeground(this);
and in Form3's constructor I have this:
private Form_FormBackground m_Parent;
public FormForeground(FormBackground parent)
{
InitializeComponent();
FormBackground m_Parent = parent;
...
}
Whenever the mouse is clicked and used to draw with in form 3,
I update the parent's rectangle like so:
private void _UpdateParent()
{
m_Parent.s_DrawArea = m_DrawArea;
m_Parent.Invalidate();
}
The parent, form 2 then does its OnPaint() where it draws the marked area.
It does work, however the drawing does lag a bit compared to drawing directly in form3 (which does not produce the desired results because the drawn area needs to be transparent across the forms).
This doesn't work because Opacity is a Property of the Form and will always make the whole form and all its content have the current Value. It is perfect for fading a form in or out, though..
You can't achieve what you want with only one form.
Instead you will need two sychronized forms.
One can be somewhat opaque and will let the desktop shine through; the other must be transparent by making use of the TransparencyKey property and you can draw onto it..
To synchronize the two forms code the Move and the ResizeEnd events.
For a first setup use code like this:
A dummy form to create the semi-transparent look:
Form form0 = new Form() { Opacity = 0.33f , BackColor = Color.Black};
In the Form1's Load event:
TransparencyKey = Color.FromArgb(255, 147, 151, 162);
BackColor = TransparencyKey;
DoubleBuffered = true;
form0.Enabled = false;
form0.BringToFront();
form0.Show();
form0.Size = Size;
form0.Location = Location;
BringToFront();
And in the Move and the ResizeEnd events maybe code like this:
private void Form1_Move(object sender, EventArgs e)
{
form0.Size = Size;
form0.Location = Location;
}
private void Form1_ResizeEnd(object sender, EventArgs e)
{
form0.Size = Size;
form0.Location = Location;
}
You also may want to study this post that also shows a way to sandwich two forms.
Note that I picked a rather random color instead of the more common named color Fuchsia or any named colors. This is because I
Don't want to accidentally use it in the drawing, thius breaking making wrong spots transparent, but also
Don't want to make the form transparent for mouse actions, aka 'click-through'. This happens when using Fuchsia (and possibly some other colors) for some weird legacy reasons..

Custom control onPaint event not working

Hey people I have a problem I am writing a custom control. My control inherits from Windows.Forms.Control and I am trying to override the OnPaint method. The problem is kind of weird because it works only if I include one control in my form if I add another control then the second one does not get draw, however the OnPaint method gets called for all the controls. So what I want is that all my custom controls get draw not only one here is my code:
If you run the code you will see that only one red rectangle appears in the screen.
public partial class Form1 : Form
{
myControl one = new myControl(0, 0);
myControl two = new myControl(100, 0);
public Form1()
{
InitializeComponent();
Controls.Add(one);
Controls.Add(two);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class myControl:Control
{
public myControl(int x, int y)
{
Location = new Point(x, y);
Size = new Size(100, 20);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen myPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(myPen, new Rectangle(Location, new Size(Size.Width - 1, Size.Height - 1)));
}
}
I'm guessing you are looking for something like this:
e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0,
this.ClientSize.Width - 1,
this.ClientSize.Height - 1));
Your Graphic object is for the interior of your control, so using Location isn't really effective here. The coordinate system starts at 0,0 from the upper-left corner of the client area of the control.
Also, you can just use the built-in Pens for colors, otherwise, if you are creating your own "new" pen, be sure to dispose of them.
LarsTech beat me to it, but you should understand why:
All drawing inside of a control is made to a "canvas" (properly called a Device Context in Windows) who coordinates are self-relative. The upper-left corner is always 0, 0.
The Width and Height are found in ClientSize or ClientRectangle. This is because a window (a control is a window in Windows), has two areas: Client area and non-client area. For your borderless/titlebar-less control those areas are one and the same, but for future-proofing you always want to paint in the client area (unless the rare occasion occurs where you want to paint non-client bits that the OS normally paints for you).

Categories