I have a panel1 with AutoScroll = true.I have to make panel1 scroll with btnUp and btnDown. So far I've made what I was asked for
private void btnUpClicked(Object sender, EventArgs e)
{
if (panel1.VerticalScroll.Value - 55 > 0)
panel1.VerticalScroll.Value -= 55;
else panel1.VerticalScroll.Value = 0;
}
private void btnDownClicked(Object sender, EventArgs e)
{
panel1.VerticalScroll.Value += 55;
}
But now I need to hide Scrollbar or make it invisible. I tried
panel1.VerticalScroll.Visible = false;
but it doesn't work. Any ideas guys?
Ok, I've done the working example of this for you. All you have to do is to change the max value depending on the total size of all the items inside your panel.
Form code:
public partial class Form1 : Form
{
private int location = 0;
public Form1()
{
InitializeComponent();
// Set position on top of your panel
pnlPanel.AutoScrollPosition = new Point(0, 0);
// Set maximum position of your panel beyond the point your panel items reach.
// You'll have to change this size depending on the total size of items for your case.
pnlPanel.VerticalScroll.Maximum = 280;
}
private void btnUp_Click(object sender, EventArgs e)
{
if (location - 20 > 0)
{
location -= 20;
pnlPanel.VerticalScroll.Value = location;
}
else
{
// If scroll position is below 0 set the position to 0 (MIN)
location = 0;
pnlPanel.AutoScrollPosition = new Point(0, location);
}
}
private void btnDown_Click(object sender, EventArgs e)
{
if (location + 20 < pnlPanel.VerticalScroll.Maximum)
{
location += 20;
pnlPanel.VerticalScroll.Value = location;
}
else
{
// If scroll position is above 280 set the position to 280 (MAX)
location = pnlPanel.VerticalScroll.Maximum;
pnlPanel.AutoScrollPosition = new Point(0, location);
}
}
}
Picture example:
You have to set AutoScroll option to False on your panel. I hope you understand what I've done and will get your panel running the way you want. Feel free to ask if you have any questions.
The Panel control takes on the duty you gave it by setting AutoScroll to true pretty serious. This always includes displaying the scrollbar gadget if it is necessary. So what you tried cannot work, hiding the vertical scrollbar forces Panel to recalculate layout since doing so altered the client area. It will of course discover that the scrollbar is required and promptly make it visible again.
The code that does this, Panel inherits it from ScrollableControl, is internal and cannot be overridden. This was intentional.
So using AutoScroll isn't going to get you anywhere. As an alternative, do keep in mind what you really want to accomplish. You simply want to move controls up and down. Easy to do, just change their Location property. That in turn is easiest to do if you put the controls on another panel, big enough to contain them. Set its AutoSize property to True. And implement you buttons' Click event handlers by simply changing that panel's Location property:
private const int ScrollIncrement = 10;
private void ScrollUpButton_Click(object sender, EventArgs e) {
int limit = 0;
panel2.Location = new Point(0,
Math.Min(limit, panel2.Location.Y + ScrollIncrement));
}
private void ScrollDownButton_Click(object sender, EventArgs e) {
int limit = panel1.ClientSize.Height - panel2.Height;
panel2.Location = new Point(0,
Math.Max(limit, panel2.Location.Y - ScrollIncrement));
}
Where panel1 is the outer panel and panel2 is the inner one that contains the controls. Be careful when you use the designer to put controls on it, it has a knack for giving them the wrong Parent. Be sure to use the View + Other Windows + Document Layout helper window so you can see this going wrong. After you filled it, set its AutoSizeMode property to GrowAndShrink so it snaps to its minimum size.
Try this:
panel.AutoScroll = true;
panel.VerticalScroll.Enabled = false;
panel.VerticalScroll.Visible = false;
Edit:
Actually when AutoScroll = true; It will take care of hscroll and vscroll automatically and you wont be able to change it. I found this on Panel.AutoScroll Property on MSDN
AutoScroll maintains the visibility of the scrollbars automatically. Therefore, setting the HScroll or VScroll property to true has no effect when AutoScroll is enabled.
You may try this to workaround this problem, I have copied it from this Link.
Behavior Observations 1:
If AutoScroll is set to true, you can't modify anything in VerticalScroll or HorizontalScroll. AutoScroll means AutoScroll; the control decides when scrollbars are visible, what the min/max is, etc. and you can't change a thing.
So if you want to customize the scrolling (e.g. hide scrollbars), you must set AutoScroll to false.
Looking at the source code for the ScrollableControl with Lutz Roeder's .NET Reflecter, you can see that if AutoScroll is set to true, it ignores your attempts to change property values within the VerticalScroll or HorizontalScroll properties such as MinValue, MaxValue, Visible etc.
Behavior Observations 2:
With AutoScroll set to false, you can change VerticalScroll.Minimum, VerticalScroll.Maximum, VerticalScroll.Visible values.
However, you cannot change VerticalScroll.Value!!! Wtf! If you set it to a non-zero value, it resets itself to zero.
Instead, you must set AutoScrollPosition = new Point( 0, desired_vertical_scroll_value );
And finally, SURPRISE, when you assign positive values, it flips them to negative values, so if you check AutoScrollPosition.X, it will be negative! Assign it positive, it comes back negative.
So yeah, if you want custom scrolling, set AutoScroll to false. Then set the VerticalScroll and HorizontalScroll properties (except Value). Then to change the scroll value, you need to set AutoScrollPosition, even though you aren't using auto scrolling! Finally, when you set the AutoScrollPosition, it will take on the opposite (i.e. negative) value that you assign to it, so if you want to retrieve the current AutoScrollPosition later, for example if you want to offset the scroll value by dragging the mouse to pan, then you need to remember to negate the value returned by AutoScrollPosition before reassigning it to AutoScrollPosition with some offset. WOW. Wtf.
One other thing, if you are trying to pan with the mouse, use the values of Cursor.Position rather than any mouse locations returned by the mouse events parameters. Scrolling the control will cause the event parameter values to be offset as well, which will cause it to start firing mouse move events complete with undesired values. Just use Cursor.Position, because it will use mouse screen coordinates as a fixed frame of reference, which is what you want when you're trying to pan/offset the scroll value.
Related
The ScrollableControl class has 2 protected boolean properties: HScroll and VScroll.
As the document says:
Gets or sets a value indicating whether the horizontal scroll bar is visible.
And
AutoScroll maintains the visibility of the scrollbars automatically. Therefore, setting the HScroll or VScroll properties to true have no effect when AutoScroll is enabled.
So I use them like this, but the scrollbar isn't showed:
class MyScrollableControl : ScrollableControl {
public MyScrollableControl() {
this.AutoScroll = false;
this.HScroll = true;
}
}
If I use the following code, it works:
this.HorizontalScroll.Visible = true;
When I put them torgether, the scrollbar is still invisible, and the values of HScroll and HorizontalScroll.Visible keep False.
this.AutoScroll = false;
this.HScroll = true;
this.HorizontalScroll.Visible = true;
So what is the real use of HScroll and VScroll ?
Update
My code and test
HScroll property does not affect scroll visibility directly, but it prevent Scroll to be hidden with HorizontalScroll.Visible value
In case when HorizontalScroll.Visible is set to true than HScroll will also get a value true (see 2nd line in the table)
In case when AutoScroll is set to true HorizontalScroll.Visible always stays true and HScroll doesnot have any influense (see last 2 lines)
Make an app with Control that contains 3 buttons with next handler code, and play with it to see what exactly happening there:
private void button1_Click(object sender, EventArgs e)
{
AutoScroll = !AutoScroll;
SetValues();
}
private void button3_Click(object sender, EventArgs e)
{
HScroll = !HScroll;
SetValues();
}
private void SetValues()
{
button1.Text = $"Auto: {(AutoScroll ? "On" : "Off")}";
button3.Text = $"HScroll: {(HScroll ? "On" : "Off")}";
button2.Text = $"Visible: {(HorizontalScroll.Visible ? "On" : "Off")}";
}
private void button2_Click(object sender, EventArgs e)
{
HorizontalScroll.Visible = !HorizontalScroll.Visible;
SetValues();
}
Usage (AutoScroll = false)
To Manually show Scroll set HorizontalScroll.Visible to true
To Manually hide Scroll set HScroll to false and than HorizontalScroll.Visible to false
Usage (AutoScroll = true)
HorizontalScroll.Visible is always true and cannot be changed
HScroll doesnot affects anything
So what is the real use of HScroll and VScroll ?
You set them to true when you have the intention of showing the scrollbars. But that is not enough, you also have to state what they should display. A scrollbar needs to know the thumb size, minimum and maximum position and current position.
You are doing battle with the internal ApplyScrollbarChanges() method. One thing it does is hide the scrollbars, even if HScroll or VScroll is set to true, if it does not have sufficient info to configure the bars. The code of the method is too large to fit here, in a nutshell it derives this info from:
The value of the AutoScrollMinWidth property.
If the control has non-default layout then it allows the layout engine of that control to determine the scroll bounds. This is only the case for the FlowLayoutPanel and TableLayoutPanel controls.
If the control has default layout then it iterates the child controls to look at their bounds.
Item 2 is an attractive customization angle, but they made the LayoutEngine class internal so you can't derive your own. Item 3 is not fundamentally different from what AutoLayout = true already does. It does work however, just add a control in the constructor, override OnClientSizeChanged() to call AdjustFormScrollbars(true) and you'll now see the scrollbar.
Which leaves item 1 to control the scrollbars. The property setter looks like this. Yup, it sneakily sets the AutoScroll property back to true :)
Simply set the AutoScrollMinSize property to control the scrollbars.
HScroll will show the horizontal scroll bar is the AutoScroll property is not defined.
In all your examples where the scroll bars do not appear, it is because you have the AutoScroll set to false, hiding the display of any scroll bars.
First off, the title might not make much sense. Suggestions for changing it are appreciated.
I am clicking into a TextBox that is inside a ScrollViewer. When that happens, the ScrollViewer will shrink in height (from the bottom up), it doesn't scroll at all, and some controls near the bottom of the viewport get covered up (cause the viewport is now smaller). If the TextBox gets covered up, I need to scroll such that it is still visible.
I have checked several SO questions, and none seem to capture my problem. This one is close, but I don't have a canvas to work with. Also, given my specific scenario, I cannot use Dispatcher to wait for the UI to load, and then use BringIntoView().
The TextBox's share an event, TextBox_GotFocus,
TextBox_GotFocus(object sender, RoutedEventArgs e)
{
myScrollViewer.Height = 400; //used to be 600
//if sender was in the 401-600 range, bring it into view
}
How do I scroll the ScrollViewer only if the entered TextBox is now hidden after the height change?
No thanks to the random downvote, but I managed to figure a roundabout way to do this.
TextBox_GotFocus(object sender, RoutedEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
//Get the distance from the top and bottom of the ScrollViewer
double offsetTop = element.TranslatePoint(new Point(), myScrollViewer).Y;
double offsetBottom = myScrollViewer.Height - offsetTop;
//Get total height needed to show the whole element
double height = 200 + element.Height;
//If the control would be hidden...
if (offsetBottom < height)
{
//Scroll down the difference
double change = myScrollViewer.VerticalOffset + (height - offsetBottom);
myScrollViewer.ScrollToVerticalOffset(change);
}
myScrollViewer.Height = 400; //used to be 600
}
When I add my UserControls to a FlowLayoutPanel, they display properly. When I change the Dock or Anchor properties on the UserControls before adding them, they are still added but do not render.
According to "How to: Anchor and Dock Child Controls" this should be possible.
I can tell that the controls are added (despite not drawing) because adding enough of them causes a vertical scrollbar to appear.
Setting the "Dock" property of the UserControls to "Left" or "None" will cause them to render, but none of the other options.
Setting the "Anchor" property on the UserControls to anything but Top | Left does not render.
Setting the dock before or after adding the control makes no difference (Add, Dock vs. Dock, Add).
The FlowLayoutPanel is itself is docked (Fill), has FlowDirection set to TopDown, has WrapContents set to false, has AutoScroll set to true, and is otherwise default.
I am using .NET 3.5.
In answer to a comment, the two commented lines are the locations I tried to change the dock. The second spot definitely makes more sense, but I tried the other because it couldn't hurt.
public void CreateObjectControl( object o )
{
ObjectControl oc = new ObjectControl();
oc.MyObject = o;
//This was a spot I mentioned:
//oc.Dock = DockStyle.Fill;
ObjectDictionary.Add( o, oc );
flowLayoutPanel1.Controls.Add( oc );
//This is the other spot I mentioned:
oc.Dock = DockStyle.Fill;
}
try using SuspendLayout and Resumelayout function for the controls before making any amendments which need rendering for proper viewing.
You could see the code from Designer.cs for that particular control
Syntax
control.SuspendLayout();
{Your code for designer amendments}
control.resumeaLayout();
I think I may have found a workaround (read: dirty trick) ... this answer helped to point me in the right direction. Here's an excerpt from the MS article that you also linked to:
For vertical flow directions, the FlowLayoutPanel control calculates the width of an implied column from the widest child control in the column. All other controls in this column with Anchor or Dock properties are aligned or stretched to fit this implied column.
The behavior works in a similar way for horizontal flow directions. The FlowLayoutPanel control calculates the height of an implied row from the tallest child control in the row, and all docked or anchored child controls in this row are aligned or sized to fit the implied row.
This page does not specifically mention that you can't Dock/Anchor the tallest/widest control. But as this control defines the layout behaviour of the FlowLayoutPanel, and thus influences the way all other sibling controls are displayed, it is well possible that Dock and Anchor don't work properly for that 'master control'. Even though I can't find any official documentation regarding that, I believe it to be the case.
So, which options do we have? At runtime, we could add a panel control of height 0 and width of the FlowLayoutPanel client area before you add your usercontrol. You can even set that panel's visibility to false. Subscribing to some Resize/Layout events of the FlowLayoutPanel to keep that panel's size will to the trick. But this does not play nicely at design time. The events won't fire and thus you can't really design the surface the way you want it to look.
I'd prefer a solution that "just works" at design time as well. So, here's an attempt at an "invisible" control that I put together, to fix the controls resizing to zero width if no other control is present. Dropping this as first control onto the FlowLayoutPanel at design time seems to provide the desired effect, and any control subsequently placed on the FlowLayoutPanel is anchorable to the right without shrinking to zero width. The only problem is that, once this invisible control is there, it seems I can't remove it anymore via the IDE. It probably needs some special treatment using a ControlDesigner to achieve that. It can still be removed in the form's designer code though.
This control, once placed onto the FlowLayoutPanel, will listen for resize events of it's parent control, and resize itself according to the ClientSize of the parent control. Use with caution, as this may contain pitfalls that didn't occur to me during the few hours I played with this. For example, I didn't try placing controls that were wider than the FlowLayoutPanel's client area.
As a side note, what will still fail is trying to anchor to the bottom, but that wasn't part of the question ;-)
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
namespace ControlTest
{
public sealed class InvisibleControl : Control
{
public InvisibleControl()
{
TabStop = false;
}
#region public interface
// Reduce the temptation ...
public new AnchorStyles Anchor
{
get { return base.Anchor; }
set { base.Anchor = AnchorStyles.None; }
}
public new DockStyle Dock
{
get { return base.Dock; }
set { base.Dock = DockStyle.None; }
}
// We don't ever want to move away from (0,0)
public new Point Location
{
get { return base.Location; }
set { base.Location = Point.Empty; }
}
// Horizontal or vertical orientation?
private Orientation _orientation = Orientation.Horizontal;
[DefaultValue(typeof(Orientation), "Horizontal")]
public Orientation Orientation
{
get { return _orientation; }
set
{
if (_orientation == value) return;
_orientation = value;
ChangeSize();
}
}
#endregion
#region overrides of default behaviour
// We don't want any margin around us
protected override Padding DefaultMargin => Padding.Empty;
// Clean up parent references
protected override void Dispose(bool disposing)
{
if (disposing)
SetParent(null);
base.Dispose(disposing);
}
// This seems to be needed for IDE support, as OnParentChanged does not seem
// to fire if the control is dropped onto a surface for the first time
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ChangeSize();
}
// Make sure we don't inadvertantly paint anything
protected override void OnPaint(PaintEventArgs e) { }
protected override void OnPaintBackground(PaintEventArgs pevent) { }
// If the parent changes, we need to:
// A) Unsubscribe from the previous parent's Resize event, if applicable
// B) Subscribe to the new parent's Resize event
// C) Resize our control according to the new parent dimensions
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
// Perform A+B
SetParent(Parent);
// Perform C
ChangeSize();
}
// We don't really want to be resized, so deal with it
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
ChangeSize();
}
#endregion
#region private stuff
// Make this a default handler signature with optional params, so that this can
// directly subscribe to the parent resize event, but also be called without parameters
private void ChangeSize(object sender = null, EventArgs e = null)
{
Rectangle client = Parent?.ClientRectangle ?? new Rectangle(0, 0, 10, 10);
Size proposedSize = _orientation == Orientation.Horizontal
? new Size(client.Width, 0)
: new Size(0, client.Height);
if (!Size.Equals(proposedSize)) Size = proposedSize;
}
// Handles reparenting
private Control boundParent;
private void SetParent(Control parent)
{
if (boundParent != null)
boundParent.Resize -= ChangeSize;
boundParent = parent;
if (boundParent != null)
boundParent.Resize += ChangeSize;
}
#endregion
}
}
How can you detect exactly when the scroll bar appears in a UserControl? Is there an event for this?
They can only appear when the control is resized or the amount of data in the control increases. Since you get notifications of resize, and adding data is up to you. It's easy to add code to test for the scrollbar in the few places where their visibility can change. There's really no need to have a special notification.
I ended up using the Layout event, and checking if the scrollbars were currently shown or not. A Layout event is sent when the scroll bar visibility changes.
This is more reliable than listening to the size of the window, because the size of the window is not the only thing that can cause the scrollbars to appear.
https://msdn.microsoft.com/en-us/library/system.windows.forms.control.layout(v=vs.110).aspx
Scroll bars are finicky
Working with scroll bars is often arduous. The Layout event solution is correct but I want to add my additional research to the knowledgebase.
I'm attempting to automatically change the width of multiple UserControl inside a custom UserControl that inherits from FlowLayoutPanel. I want a vertical scroll bar to appear only when the list is longer than the panel size. No horizontal scroll bar ever. Your implementation may differ slightly but the bulk of code will be similar and face similar issues.
ScrollableControl
In order for a UserControl to have a scroll bar appear, it must inherit from ScrollableControl. Both Panel and ContainerControl fit this criteria.
ScrollableControl only contains the Scroll event. This event occurs when scrolling but not upon appearance of the scroll bar.
Rather, the Layout Event found inside Control will occur when a control should reposition its child controls. This includes resizing, child resizing, and parent resizing. I would recommend using this event rather than manually checking for resize to avoid unwanted and inconsistent behavior.
Detecting a scroll bar
To detect when the scroll bar should appears, I count the number of controls in the FlowLayoutPanel and compare it against the number of "visible controls". Visible controls are those which intersect the border area of the panel.
private void RichFlowPanel_Layout(object sender, LayoutEventArgs e)
{
var controls = Controls.Cast<Control>().OrderBy(x => x.Top);
var visibles = controls.Where(l => ClientRectangle.IntersectsWith(l.Bounds));
if (visibles.Count() <= Controls.Count)
{
// A scrollbar exists
}
else
{
// A scrollbar does not exist
}
}
Derived from this answer.
Controlling a scroll bar
A scroll bar can automatically show/hide by setting AutoScroll=true. This will also display the horizontal scrollbar if there isn't space for the vertical scrollbar. AutoScroll opens a Pandora's box of scrollbar issues & bugs. In order to keep the horizontal scrollbar hidden, AutoScroll must be false. This answer outlines a work around for keeping the horizontal hidden.
Specifically
panel.HorizontalScroll.Maximum = 0;
HScroll = false;
panel.VerticalScroll.Visible = false;
will hide the horizontal scroll bar.
The usage and odd behavior of HScroll is covered in this answer.
Combining what we've learned
The following event method is attached to the Layout event inside my custom user control extending FlowLayoutPanel.
Point prevPosition;
private void RichFlowPanel_Layout(object sender, LayoutEventArgs e)
{
var controls = Controls.Cast<Control>().OrderBy(x => x.Top);
var visibles = controls.Where(l => ClientRectangle.IntersectsWith(l.Bounds));
prevPosition = AutoScrollPosition;
if (visibles.Count() <= Controls.Count)
{
Console.WriteLine("showing scroll bar" + " V: " + visibles.Count() + " C: " + Controls.Count);
VerticalScroll.Visible = true;
// Insert method here to tell children controls to resize
HorizontalScroll.Maximum = 0;
HScroll = false;
HorizontalScroll.Visible = false;
}
else
{
Console.WriteLine("hiding scroll bar" + " V: " + visibles.Count() + " C: " + Controls.Count);
VScroll = false;
VerticalScroll.Visible = false;
// Insert method here to tell children controls to resize
HorizontalScroll.Maximum = 0;
HScroll = false;
HorizontalScroll.Visible = false;
}
AutoScrollPosition = new Point(Math.Abs(AutoScrollPosition.X), Math.Abs(prevPosition.Y));
}
Additionally, InitalializeComponents() of the FlowLayoutPanel for completeness and because docking, AutoSize, etc. can often bring confusing behavior.
this.SuspendLayout();
//
// RichFlowPanelUserControl
//
this.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.AutoSize = true;
this.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.Padding = new System.Windows.Forms.Padding(3);
this.WrapContents = false;
this.Layout += new System.Windows.Forms.LayoutEventHandler(this.RichFlowPanel_Layout);
this.ResumeLayout(false);
I understand this is slightly off-topic but the combination of this information should help users on their scroll bar adventures.
Why not use the "ClientSizeChanged" event?
This event gets fired if the client size has changed, which is the case if a scrollbar is added.
I need to allow a long label to be scrolled through on it's own. I do not want a text-box of any sort. I would like to be able to format the text inside. It definitely needs to scroll own its own, not with the window. I have added a scrollbar successfully, but I have no idea how to begin to use it's event/s.
thanks
i tried using a panel? I will again, perhaps I made an error.
:: yeah I tried that again, it simply cuts off my label.
Place the label inside a Panel and set AutoScroll to true.
Add a label (here label1) and a scrollbar (here hScrollBar1) and deal with the event in this fashion (assuming hScrollBar1.Maximum = 100 and hScrollBar1.Minimum = 0):
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
const int labellength = 10;
String thetext = "Ozzie ozzie ozzie! OI OI OI! And then some...";
int offset = (int)((double)e.NewValue / 100 * (thetext.Length - labellength));
label1.Text = thetext.Substring(offset, labellength);
}
Naturally you would have to specify the 'amount' of text to appear in the label by changing labellength. If you find that you can not scroll to the very end, lower hScrollBar1.LargeChange to 1.