I'm using the following code as just a test for how I might use a custom progress bar in the future - it actually isn't a progress bar at all, but rather a picturebox that the code draws a rectangle on, and then fills up based on a timer.
The problem is, I'm reaching 100% before the box is filled. I've tinkered around, but not been able to locate the issue. What am I doing wrong? See code below, and imgur screenshot of the behavior on my system.
Thanks.
public partial class frmLoading : Form
{
System.Windows.Forms.Timer tLoading = new System.Windows.Forms.Timer();
Double pbLoadingUnit;
int pbLoadingWIDTH, pbLoadingHEIGHT, pbLoadingComplete;
Bitmap bmpLoading;
Graphics gLoading;
private void frmLoading_Load(object sender, EventArgs e)
{
pbLoadingWIDTH = pictureLoading.Width;
pbLoadingHEIGHT = pictureLoading.Height;
pbLoadingUnit = pbLoadingWIDTH / 100;
pbLoadingComplete = 0;
bmpLoading = new Bitmap(pbLoadingWIDTH, pbLoadingHEIGHT);
tLoading.Interval = 32;
tLoading.Tick += new EventHandler(this.tLoading_Tick);
tLoading.Start();
}
private void tLoading_Tick(object sender, EventArgs e)
{
gLoading = Graphics.FromImage(bmpLoading);
gLoading.Clear(Color.DarkSlateGray);
gLoading.FillRectangle(Brushes.DodgerBlue, new Rectangle(0, 0, (int)(pbLoadingComplete * pbLoadingUnit), pbLoadingHEIGHT));
gLoading.DrawString(pbLoadingComplete + "%", new Font("Segoe UI Semibold", pbLoadingHEIGHT / 2), Brushes.White, new PointF(pbLoadingWIDTH / 2 - pbLoadingHEIGHT, pbLoadingHEIGHT / 10));
pictureLoading.Image = bmpLoading;
pbLoadingComplete++;
if (pbLoadingComplete > 100)
{
gLoading.Dispose();
tLoading.Stop();
}
}
You should change this
pbLoadingUnit = pbLoadingWIDTH / 100;
to this
pbLoadingUnit = Convert.ToDouble(pbLoadingWIDTH) / 100;
I assume your picture width is not a multiple of a hundred.
With your old code, your pbLoadingUnit will be generate as integer since you divide integer to integer.
You may use this too :
double div = 100;
pbLoadingUnit = pbLoadingWIDTH / div;
OR
pbLoadingUnit = pbLoadingWIDTH / Convert.ToDouble(100);
The point is, you should get the double value of pbLoadingUnit.
For more information about numeric casting, you may see this link dotnetperls.com/numeric-casts
Try to put the following lines in OnResize() event of the frmLoading:
pbLoadingWIDTH = pictureLoading.Width;
pbLoadingHEIGHT = pictureLoading.Height;
pbLoadingUnit = pbLoadingWIDTH / 100;
Chances are you're getting the initial size of the pictureLoading during Load()... and not the actual width when it shows up and displayed in your form.
Also let tloading.Start() happens when you already get the appropriate size of you pictureLoading object.
you have to add this variable int pbmodal;
then calculate MOD of the unit
on form load add
pbmodal = pbLoadingWIDTH % 100;
when the completed is 100% add the modal.
Difference of division
`if (pbLoadingComplete > 100)
{
gLoading.FillRectangle(Brushes.DodgerBlue, new Rectangle(0, 0, (int)(pbLoadingComplete * pbLoadingUnit) + pbmodal, pbLoadingHEIGHT));
gLoading.DrawString(pbLoadingComplete-1 + "%", new Font("Segoe UI Semibold", pbLoadingHEIGHT / 2), Brushes.White, new PointF(pbLoadingWIDTH / 2 - pbLoadingHEIGHT, pbLoadingHEIGHT / 10));
pictureLoading.Image = bmpLoading;
}`
I am developing a Windows desktop application dual monitor where I need to display my form sometimes on primary screen and sometimes on secondary,
which works fine but when I display it on my secondary screen I want it to be displayed on centre of my screen which is not working.
Here is my code:
if (Screen.AllScreens.Length > 1)
myForm.Location = Screen.AllScreens[1].WorkingArea.Location;
myForm.StartPosition = FormStartPosition.Manual; // because i wrote manual it is displayed on Top left of my secondaryScreen which is ok
myForm.show();
but I want to diplay it on centre so I wrote
myForm.StartPosition = FormStartPosition.CentreScreen;
//it is not working again a form is displayed on Centre of PrimaryScreen..
Any idea why?
You cannot use StartPosition.CenterScreen because that picks the monitor on which the mouse is currently located. Usually desirable but not what you are asking for. You must use the form's Load event to move it where you want it. Using form's Load is important, you do not know the size of the window until after it is created and the user's preferences are applied and it is rescaled to match the video DPI.
Boilerplate code should look like this:
private void button1_Click(object sender, EventArgs e) {
var form = new Form2();
form.Load += CenterOnSecondMonitor;
form.Show();
}
private void CenterOnSecondMonitor(object sender, EventArgs e) {
var form = (Form)sender;
var area = Screen.AllScreens.Length > 1 ? Screen.AllScreens[1].WorkingArea : Screen.PrimaryScreen.WorkingArea;
form.Location = new Point((area.Width - form.Width) / 2, (area.Height - form.Height) / 2);
form.Load -= CenterOnSecondMonitor;
}
Or you put this code into the form itself, the more common choice:
protected override void OnLoad(EventArgs e) {
var area = Screen.AllScreens.Length > 1 ? Screen.AllScreens[1].WorkingArea : Screen.PrimaryScreen.WorkingArea;
this.Location = new Point((area.Width - this.Width) / 2, (area.Height - this.Height) / 2);
base.OnLoad(e);
}
look for the property of your winform named StartPosition then set it into Center Screen
You could write an extension method:
public static void MoveForm(this Form form, Screen screen = null)
{
if(screen == null)
{
//If we have a single screen, we are not moving the form
if(Screen.AllScreens.Length > 1) return;
screen = Screen.AllScreens[1];
}
var bounds = screen.Bounds;
form.Left = ((bounds.Left + bounds.Right) / 2) - (form.Width / 2);
form.Top = ((bounds.Top + bounds.Bottom) / 2) - (form.Height / 2);
}
private void CenterOnTheCurrentScreen()
{
Rectangle workingArea = Screen.FromControl(this).WorkingArea;
Point center = new Point((workingArea.Width - this.Width) / 2, (workingArea.Height - this.Height) / 2);
this.Location = new Point(workingArea.X + center.X, workingArea.Y + center.Y);
}
I have a custom control that zooms on a custom drawn document canvas.
I tried using AutoScroll but it was not giving satisfactory results. When I would set AutoScrollPosition and AutoScrollMinSize back to back (in any order) it would force a paint and cause jitter each time the zoom changes. I assume this was because it was calling an Update and not Invalidate when I modified both properties.
I am now manually setting the HorizontalScroll and VerticalScroll properties with AutoScroll set to false like so each time the Zoom level or the client size changes:
int canvasWidth = (int)Math.Ceiling(Image.Width * Zoom) + PageMargins.Horizontal;
int canvasHeight = (int)Math.Ceiling(Image.Height * Zoom) + PageMargins.Vertical;
HorizontalScroll.Maximum = canvasWidth;
HorizontalScroll.LargeChange = ClientSize.Width;
VerticalScroll.Maximum = canvasHeight;
VerticalScroll.LargeChange = ClientSize.Height;
if (canvasWidth > ClientSize.Width)
{
HorizontalScroll.Visible = true;
}
else
{
HorizontalScroll.Visible = false;
HorizontalScroll.Value = 0;
}
if (canvasHeight > ClientSize.Height)
{
VerticalScroll.Visible = true;
}
else
{
VerticalScroll.Visible = false;
VerticalScroll.Value = 0;
}
int focusX = (int)Math.Floor((FocusPoint.X * Zoom) + PageMargins.Left);
int focusY = (int)Math.Floor((FocusPoint.Y * Zoom) + PageMargins.Top);
focusX = focusX - ClientSize.Width / 2;
focusY = focusY - ClientSize.Height / 2;
if (focusX < 0)
focusX = 0;
if (focusX > canvasWidth - ClientSize.Width)
focusX = canvasWidth - ClientSize.Width;
if (focusY < 0)
focusY = 0;
if (focusY > canvasHeight - ClientSize.Height)
focusY = canvasHeight - ClientSize.Height;
if (HorizontalScroll.Visible)
HorizontalScroll.Value = focusX;
if (VerticalScroll.Visible)
VerticalScroll.Value = focusY;
In this case, FocusPoint is a PointF structure that holds the coordinates in the bitmap which the user is focused on (for example, when they mouse wheel to zoom in they are focusing on the current mouse location at that time). This functionality works for the most part.
What does not work is the scroll bars. If the user tries to manually scroll by clicking on either scroll bar, they both keep returning to 0. I do not set them anywhere else in my code. I have tried writing the following in the OnScroll() method:
if (se.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
VerticalScroll.Value = se.NewValue;
}
else
{
HorizontalScroll.Value = se.NewValue;
}
Invalidate();
But this causes some very erratic behavior including flicking and scrolling out of bounds.
How am I supposed to write the code for OnScroll? I've tried the base.OnScroll but it didn't do anything while AutoScroll is set to false.
I ended up implementing my own custom scrolling by creating 3 child controls: an HScrollBar, a VScrollBar, and a Panel.
I hide ClientSize and ClientRectangle like so:
public new Rectangle ClientRectangle
{
get
{
return new Rectangle(new Point(0, 0), ClientSize);
}
}
public new Size ClientSize
{
get
{
return new Size(
base.ClientSize.Width - VScrollBar.Width,
base.ClientSize.Height - HScrollBar.Height
);
}
}
The layout is done in OnClientSizeChanged:
protected override void OnClientSizeChanged(EventArgs e)
{
base.OnClientSizeChanged(e);
HScrollBar.Location = new Point(0, base.ClientSize.Height - HScrollBar.Height);
HScrollBar.Width = base.ClientSize.Width - VScrollBar.Width;
VScrollBar.Location = new Point(base.ClientSize.Width - VScrollBar.Width, 0);
VScrollBar.Height = base.ClientSize.Height - HScrollBar.Height;
cornerPanel.Size = new Size(VScrollBar.Width, HScrollBar.Height);
cornerPanel.Location = new Point(base.ClientSize.Width - cornerPanel.Width, base.ClientSize.Height - cornerPanel.Height);
}
Each ScrollBar has their Scroll event subscribed to the following:
private void ScrollBar_Scroll(object sender, ScrollEventArgs e)
{
OnScroll(e);
}
And finally we can allow MouseWheel events to scroll with the following:
protected override void OnMouseWheel(MouseEventArgs e)
{
int xOldValue = VScrollBar.Value;
if (e.Delta > 0)
{
VScrollBar.Value = (int)Math.Max(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), 0);
OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
}
else
{
VScrollBar.Value = (int)Math.Min(VScrollBar.Value - (VScrollBar.SmallChange * e.Delta), VScrollBar.Maximum - (VScrollBar.LargeChange - 1));
OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, xOldValue, VScrollBar.Value, ScrollOrientation.VerticalScroll));
}
}
For custom painting, you would use the following statement:
e.Graphics.TranslateTransform(-HScrollBar.Value, -VScrollBar.Value);
This worked perfectly without the glitches present when using AutoScroll.
In my current project I have a problem when I add my usercontrol into the panel of splitcontainer. I managed to add it to the middle of the panel with the following code:
ucFactuur ucFactuur = new ucFactuur();
ucFactuur.Location = new Point(
splitContainer1.Panel2.ClientSize.Width / 2 - ucFactuur.Size.Width / 2,
splitContainer1.Panel2.ClientSize.Height / 2 - ucFactuur.Size.Height / 2);
ucFactuur.Anchor = AnchorStyles.None;
splitContainer1.Panel2.Controls.Add(ucFactuur);
But now my scrollbar is gone, it is there when I remove the AnchorStyles.None but then when I resize the window it doesn't stay in the middle (It's in a fixed position).
I'm uncertain how to resolve this problem, nor can I find any other way to dynamically center my usercontrol.
Thanks,
Thomas
Anchoring.None won't work in this situation since it only works when there are no scrollbars. But once you have scrollbars, you don't want the control centered anymore, you need it positioned against the scroll value.
In other words, I think you have to handle the resizing yourself:
private void DoResize(object sender, EventArgs e) {
splitContainer1.Panel2.AutoScrollMinSize = ucFactuur.Size;
if (ucFactuur.Width < splitContainer1.Panel2.ClientSize.Width) {
ucFactuur.Left = splitContainer1.Panel2.ClientSize.Width / 2 -
ucFactuur.Width / 2;
} else {
ucFactuur.Left = splitContainer1.Panel2.AutoScrollPosition.X;
}
if (ucFactuur.Height < splitContainer1.Panel2.ClientSize.Height) {
ucFactuur.Top = splitContainer1.Panel2.ClientSize.Height / 2 -
ucFactuur.Height / 2;
} else {
ucFactuur.Top = splitContainer1.Panel2.AutoScrollPosition.Y;
}
}
Then your setup would change to this:
ucFactuur ucFactuur = new ucFactuur();
ucFactuur.AutoSize = true;
ucFactuur.Resize += DoResize;
splitContainer1.Panel2.Resize += DoResize;
splitContainer1.Panel2.AutoScroll = false;
splitContainer1.Panel2.Controls.Add(ucFactuur);
There used to be 3 dots in the splitter bar of a SplitContainer. Just like there are three lines in question details text box on StackOverflow that shows it can be grabbed. How can I do this with the splitter bar of a SplitContainer in .NET?
Not that I have anything against Alex's answer, but I thought I'd share this solution as it looks a bit nicer to me (on an XP machine anyway?).
private void SplitContainer_Paint(object sender, PaintEventArgs e)
{
var control = sender as SplitContainer;
//paint the three dots'
Point[] points = new Point[3];
var w = control.Width;
var h = control.Height;
var d = control.SplitterDistance;
var sW = control.SplitterWidth;
//calculate the position of the points'
if (control.Orientation == Orientation.Horizontal)
{
points[0] = new Point((w / 2), d + (sW / 2));
points[1] = new Point(points[0].X - 10, points[0].Y);
points[2] = new Point(points[0].X + 10, points[0].Y);
}
else
{
points[0] = new Point(d + (sW / 2), (h / 2));
points[1] = new Point(points[0].X, points[0].Y - 10);
points[2] = new Point(points[0].X, points[0].Y + 10);
}
foreach (Point p in points)
{
p.Offset(-2, -2);
e.Graphics.FillEllipse(SystemBrushes.ControlDark,
new Rectangle(p, new Size(3, 3)));
p.Offset(1, 1);
e.Graphics.FillEllipse(SystemBrushes.ControlLight,
new Rectangle(p, new Size(3, 3)));
}
}
Hope this pleases someone? Haa!
That isn't implemented. If you'd like that feature, it's best you derive the SplitContainer and override the OnPaint method.
Update 1
Here's some code to do what you requested. It is in VB.NET and the dot placement can do with some tweaking. Overall, the code works as expected.
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing
Public Class SplitContainerEx
Inherits SplitContainer
''' <summary>Determines the thickness of the splitter.</summary>
<DefaultValue(GetType(Integer), "5"), Description("Determines the thickness of the splitter.")> _
Public Overridable Shadows Property SplitterWidth() As Integer
Get
Return MyBase.SplitterWidth
End Get
Set(ByVal value As Integer)
If value < 5 Then value = 5
MyBase.SplitterWidth = value
End Set
End Property
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
'paint the three dots
Dim points(2) As Point
Dim pointRect = Rectangle.Empty
'calculate the position of the points
If Orientation = Windows.Forms.Orientation.Horizontal Then
points(0) = New Point((MyBase.Width \ 2), SplitterDistance + (SplitterWidth \ 2))
points(1) = New Point(points(0).X - 10, points(0).Y)
points(2) = New Point(points(2).X + 10, points(0).Y)
pointRect = New Rectangle(points(1).X - 2, points(1).Y - 2, 25, 5)
Else
points(0) = New Point(SplitterDistance + (SplitterWidth \ 2), (MyBase.Height \ 2))
points(1) = New Point(points(0).X, points(0).Y - 10)
points(2) = New Point(points(0).X, points(0).Y + 10)
pointRect = New Rectangle(points(1).X - 2, points(1).Y - 2, 5, 25)
End If
e.Graphics.FillRectangle(Brushes.Gray, pointRect)
For Each p In points
p.Offset(-1, -1)
e.Graphics.FillEllipse(Brushes.Black, New Rectangle(p, New Size(3, 3)))
Next
End Sub
End Class
Update 2
I'm putting up the C# equivalent because you tagged your question so.
If vb makes you sick, learn to head over to Convert VB.NET to C# - Developer Fusion and do the VB to C# conversion.
using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
public class SplitContainerEx : SplitContainer
{
/// <summary>Determines the thickness of the splitter.</summary>
[DefaultValue(typeof(int), "5"), Description("Determines the thickness of the splitter.")]
public virtual new int SplitterWidth {
get { return base.SplitterWidth; }
set {
if (value < 5)
value = 5;
base.SplitterWidth = value;
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
//paint the three dots
Point[] points = new Point[3];
Rectangle pointRect = Rectangle.Empty;
//calculate the position of the points
if (Orientation == System.Windows.Forms.Orientation.Horizontal) {
points[0] = new Point((int)(base.Width / 2), SplitterDistance + (int)(SplitterWidth / 2));
points[1] = new Point(points[0].X - 10, points[0].Y);
points[2] = new Point(points[2].X + 10, points[0].Y);
pointRect = new Rectangle(points[1].X - 2, points[1].Y - 2, 25, 5);
} else {
points[0] = new Point(SplitterDistance + (int)(SplitterWidth / 2), (int)(base.Height / 2));
points[1] = new Point(points[0].X, points[0].Y - 10);
points[2] = new Point(points[0].X, points[0].Y + 10);
pointRect = new Rectangle(points[1].X - 2, points[1].Y - 2, 5, 25);
}
e.Graphics.FillRectangle(Brushes.Gray, pointRect);
foreach (Point p in points) {
p.Offset(-1, -1);
e.Graphics.FillEllipse(Brushes.Black, new Rectangle(p, new Size(3, 3)));
}
}
}
The closest you can come without painting it yourself is to change the BorderStyle to Fixed3D. That will give you a sort of "bar" in between the two panels.
If you don't like the sunken look on the two panels themselves, you can sort of "hide" the outer borders by putting your splitpanel inside of another panel and setting its Location to a negative value (e.g. -n,-n) and its size to its parent panel size + 2*n. Then I'd set the Anchor to Top | Left | Bottom | Right so that it stays that way as you resize the parent panel.
It's kind of a kludge, but something I've certainly considered doing as I hate that there's no indication of where the "grab point" is.
I liked shousper's and Alex's answers, but they seemed a little 'complex' for my taste; there seemed to be more code that I would have thought necessary.
Shaun's answer also works (I also found that one in MSDN), but in the application I'm working on, the 'grab handles' became quite distracting, since they almost fill the splitter, and we have quite a few of them.
So I came up with this, which is in-between. No arrays, no new rectangles. Would take very little additional work for the '3D' effect from the 'accepted' answer, but the 3 plain dots worked for me:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
Point centerPoint = new Point(SplitterRectangle.Left - 1 + SplitterRectangle.Width / 2, SplitterRectangle.Top - 1 + SplitterRectangle.Height / 2);
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y, 3, 3);
if (Orientation == System.Windows.Forms.Orientation.Horizontal) {
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X - 10, centerPoint.Y, 3, 3);
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X + 10, centerPoint.Y, 3, 3);
} else {
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y - 10, 3, 3);
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y + 10, 3, 3);
}
}
I didn't really want to do all the grunt work of drawing a grab handle as I was writing an in-house admin tool and that was overkill. As per this article on MSDN you can sub-class the SplitContainer, override the OnPaint(PaintEventArgs) method and include the following line:
ControlPaint.DrawGrabHandle(e.Graphics, SplitterRectangle, true, Enabled);
This will draw a basic dividing line between the panes.
What I prefer is to just add a paint handler. This means that you don't need to derive a new class and the static function is easily reusable if you put it into a file that can be shared by various projects - just remember to use "add as link" so that if you edit the file, it will be changed for all projects that include it. The main fault with this is it doesn't automatically handle changing the color of the control.
...
mySplitContainer.Paint += CustomPaint.PaintSplitterWithHandle;
...
public static class CustomPaint {
public static void PaintSplitterWithHandle(object sender, PaintEventArgs p) {
SplitContainer splitter = sender as SplitContainer;
if (splitter == null) return;
if (splitter.Orientation == Orientation.Horizontal)
p.Graphics.DrawLine(Pens.DarkGray,
0, splitter.SplitterDistance + (splitter.SplitterWidth / 2),
splitter.Width, splitter.SplitterDistance + (splitter.SplitterWidth / 2));
else
p.Graphics.DrawLine(Pens.DarkGray,
splitter.SplitterDistance + (splitter.SplitterWidth / 2), 0,
splitter.SplitterDistance + (splitter.SplitterWidth / 2), splitter.Height);
}
}