How to Programmatically Scroll a Panel - c#

I have a System.Windows.Forms.Panel with some content.
I am trying to programmatically scroll the panel (vertically) either up or down.
I have tried setting the AutoScrollPosition property to a new Point on the panel but that doesn't seem to do it.
I have the AutoScroll property set to true.
I even tried to set the VerticalScroll.Value twice as suggested here, but that doesn't seem to work either.
This is what I am currently doing:
//I have tried passing both positive and negative values.
panel.AutoScrollPosition = new Point(5, 10);
The X and Y values on AutoScrollPosition remain 0 and 0.
Any help or direction on this would be greatly appreciated it.
Thanks in advance,
Marwan

Here is a solution. I guess you can scroll your Panel by arbitrary position using Win32 however there is a simple trick to help you achieve your requirement here:
public void ScrollToBottom(Panel p){
using (Control c = new Control() { Parent = p, Dock = DockStyle.Bottom })
{
p.ScrollControlIntoView(c);
c.Parent = null;
}
}
//use the code
ScrollToBottom(yourPanel);
Or use extension method for convenience:
public static class PanelExtension {
public static void ScrollToBottom(this Panel p){
using (Control c = new Control() { Parent = p, Dock = DockStyle.Bottom })
{
p.ScrollControlIntoView(c);
c.Parent = null;
}
}
}
//Use the code
yourPanel.ScrollToBottom();
UPDATE
If you want to set the exact position, modifying the code above a little can help:
//This can help you control the scrollbar with scrolling up and down.
//The position is a little special.
//Position for scrolling up should be negative.
//Position for scrolling down should be positive
public static class PanelExtension {
public static void ScrollDown(this Panel p, int pos)
{
//pos passed in should be positive
using (Control c = new Control() { Parent = p, Height = 1, Top = p.ClientSize.Height + pos })
{
p.ScrollControlIntoView(c);
}
}
public static void ScrollUp(this Panel p, int pos)
{
//pos passed in should be negative
using (Control c = new Control() { Parent = p, Height = 1, Top = pos})
{
p.ScrollControlIntoView(c);
}
}
}
//use the code, suppose you have 2 buttons, up and down to control the scrollbar instead of clicking directly on the scrollbar arrows.
int i = 0;
private void buttonUp_Click(object sender, EventArgs e)
{
if (i >= 0) i = -1;
yourPanel.ScrollUp(i--);
}
private void buttonDown_Click(object sender, EventArgs e)
{
if (i < 0) i = 0;
yourPanel.ScrollDown(i++);
}
Another solution you may want to use is using Panel.VerticalScroll.Value. However I think you need more research to make it work as you expect. Because I can see once changing the Value, the scrollbar position and control position don't sync well. Notice that Panel.VerticalScroll.Value should be between Panel.VerticalScroll.Minimum and Panel.VerticalScroll.Maximum.

This surprisingly works! NOTE THE MINUS SIGN in the code. There is strange behavior in setting scroll position. If you set the position to exact value (50), it goes negative when you read it next time (-50). So you have to invert it before setting new scroll value.
Scroll down:
private void ButtonScrollDown_OnClick(object sender, EventArgs e)
{
Point current = yourScrollPanel.AutoScrollPosition;
Point scrolled = new Point(current.X, -current.Y + 10);
yourScrollPanel.AutoScrollPosition = scrolled;
}
Scroll up similarly, (-current.Y - 10)

If you have a class that derives from Panel, then call these two protected methods to scroll the panel:
// The bottom is off screen; scroll down. These coordinates must be negative or zero.
SetDisplayRectLocation(0, AutoScrollPosition.Y - item.BoundingRect.Bottom + ClientRectangle.Bottom);
AdjustFormScrollbars(true);
In my example, item.BoundingRect.Bottom is the Y coordinate of the bottom of a thumbnail, and I need to scroll the panel down so that the whole thumbnail is visible.
#King King's solution of creating a temporary Control just so that scrolling could be done seemed "heavy" to me. And #Hans Passant's suggestion of setting AutoScrollMinSize and AutoScrollPosition didn't work for me.
Leave AutoScroll to its default value of 'true'.

Try this:-
panel.ScrollControlIntoView(childcontrol);
This should work. childcontrol is the particular control that you want to show in your display area.

Setting the value of the HorizontalScroll property and then using the method ScrollControlIntoView works for me:
lpanel.HorizontalScroll.Value = 100;
lpanel.ScrollControlIntoView(lpanel);

Use #King King Answered Code and if you want to hide horizontal and vertical scroll bar, just apply the below code in the constructor or initialization.
yourPanel.AutoScroll = false;
yourPanel.HorizontalScroll.Maximum = 0;
yourPanel.HorizontalScroll.Visible = false;
yourPanel.VerticalScroll.Maximum = 0;
yourPanel.VerticalScroll.Visible = false;
yourPanel.AutoScroll = true;

I had an issue where I couldnt get my panel to scroll back to top . I tried many things to try and get the panel to scroll back to the top after populating it with many controls.
Nomatter what I did it always put the VScroll bar to the bottom.
After exhaustive testing I found it was because my controls had the TabStop property set to true (default on user controls) was causing the issue.
Setting TabStop to false fixed it.

Create an control that sits slightly outside the visible area (so -1 at the top and clientsize+1 ) and then call ScrollControlIntoView:
public static class PanelExtension {
public static void ScrollDown(this Panel p)
{
using (Control c = new Control() { Parent = p, Height = 1, Top = p.ClientSize.Height + 1 })
{
p.ScrollControlIntoView(c);
}
}
public static void ScrollUp(this Panel p )
{
using (Control c = new Control() { Parent = p, Height = 1, Top = -1})
{
p.ScrollControlIntoView(c);
}
}
}
//use the code, suppose you have 2 buttons, up and down to control the scrollbar instead of clicking directly on the scrollbar arrows.
private void buttonUp_Click(object sender, EventArgs e)
{
yourPanel.ScrollUp();
}
private void buttonDown_Click(object sender, EventArgs e)
{
yourPanel.ScrollDown();
}
with yourpanel.SetAutoScrollMargin(1, 1); you can set very fine scrolling steps and then take a timer to call the srolling when buttons are down

Related

Location of panel not correct when using different anchor style

this is for Winforms / C# btw.
So I am making my own ComboBox, just playing around.
The item List is basically a Panel, which is located under or above the ComboBox control, depending on its height and location.
This works as intended, as long as I use AnchorStyle Top / Left for my usercontrol.
As soon as I switch to Top / Right Style, the location of my listpanel breaks. Okay, makes sense, the location point of my usercontrol changes with the different anchorstyle, so how about I adjust my panel to a new location using my usercontrol.LocationChanged event? Doesen't work. Maybe because of the order of the events? Or does the origin of locations change depending on the given AnchorStyles?
Anyway, I am a bit lost. Unfortunately I couldn't find a similar case here, therefore this question.
Here's my DropDownPanel (effectively my "list"):
private Panel DropDownPanel()
{
Panel DropDownPanel = new Panel() {
Width = this.Width,
Height = 200,
BackColor = _skin.TextBoxBackColor,
BorderStyle = BorderStyle.FixedSingle,
ForeColor = _skin.TextBoxForeColor,
Visible = false,
AutoScroll = false
};
DropDownPanel.HorizontalScroll.Enabled = false;
DropDownPanel.HorizontalScroll.Visible = false;
DropDownPanel.HorizontalScroll.Maximum = 0;
DropDownPanel.AutoScroll = true;
DropDownPanel.Leave += DropDownPanel_Leave;
return DropDownPanel;
}
Here's my userControl Load Event:
private void GbComboBox_Load(object sender, EventArgs e)
{
_dropDownPanel = DropDownPanel();
this.Parent.Parent.Controls.Add(_dropDownPanel);
RelocateDropDownPanel();
_dropDownPanel.BringToFront();
ApplyItems(_items);
}
And my calculation for the location:
private void RelocateDropDownPanel()
{
if (_dropDownPanel != null)
{
int initLocY = this.Parent.Location.Y + this.Location.Y + this.Height + _dropDownPanel.Height;
int fullHeight = this.FindForm().Height;
Point p = new Point();
if (initLocY < fullHeight)
{
p = new Point(this.Parent.Location.X + this.Location.X, initLocY - _dropDownPanel.Height);
}
else
{
p = new Point(this.Parent.Location.X + this.Location.X, initLocY - _dropDownPanel.Height - this.Height);
}
_dropDownPanel.Location = p;
}
}
So I figured out a "solution".
I simply inherited the Anchor of my Panel from the Usercontrol it was associated to.
But I wanna mention, that still, this whole thing is kind of a crappy way of dealing with custom combobox desings. As #Jimi stated in his first comment, the correct way of doing this would be to ownerdraw the combobox. I haven't done this because of specific reasons, which didn't let me do this.
A little example on how this could be made:
Change ComboBox Border Color in Windows Forms

Expander menu in C #

I am trying to reproduce the operation of the Control Expander WPF, or as shown in the menu of Outlook, Vertical Web Menu etc., since in WindowsForms this control does not exist. Here I leave the sample code: Menu_Expader.zip link GoogleDrive.
I have managed to do it using the following controls:
Panels
FlowLayoutPanel
1 Time Control
Button Vectors
Labels Vectors ...
This works perfectly, but it happens that to each panel I must establish a
Maximum Size and Minimum Size therefore every time I add an item inside I must modify the size of the panel where I add it, and the item are very close to each other is a bit annoying for the user's vision.
Example this is what I currently have:
EDIT
Code Sample:
// The state of an expanding or collapsing panel.
private enum ExpandState
{
Expanded,
Expanding,
Collapsing,
Collapsed,
}
// The expanding panels' current states.
private ExpandState[] ExpandStates;
// The Panels to expand and collapse.
private Panel[] ExpandPanels;
// The expand/collapse buttons.
private Button[] ExpandButtons;
// Initialize.
private void Form1_Load(object sender, EventArgs e)
{
// Initialize the arrays.
ExpandStates = new ExpandState[]
{
ExpandState.Expanded,
ExpandState.Expanded,
ExpandState.Expanded,
};
ExpandPanels = new Panel[]
{
panModule1,
panModule2,
panModule3,
};
ExpandButtons = new Button[]
{
btnExpand1,
btnExpand2,
btnExpand3,
};
// Set expander button Tag properties to give indexes
// into these arrays and display expanded images.
for (int i = 0; i < ExpandButtons.Length; i++)
{
ExpandButtons[i].Tag = i;
ExpandButtons[i].Image = Properties.Resources.expander_down;
}
}
// Start expanding.
private void btnExpander_Click(object sender, EventArgs e)
{
// Get the button.
Button btn = sender as Button;
int index = (int)btn.Tag;
// Get this panel's current expand
// state and set its new state.
ExpandState old_state = ExpandStates[index];
if ((old_state == ExpandState.Collapsed) ||
(old_state == ExpandState.Collapsing))
{
// Was collapsed/collapsing. Start expanding.
ExpandStates[index] = ExpandState.Expanding;
ExpandButtons[index].Image = Properties.Resources.expander_up;
}
else
{
// Was expanded/expanding. Start collapsing.
ExpandStates[index] = ExpandState.Collapsing;
ExpandButtons[index].Image = Properties.Resources.expander_down;
}
// Make sure the timer is enabled.
tmrExpand.Enabled = true;
}
// The number of pixels expanded per timer Tick.
private const int ExpansionPerTick = 7;
// Expand or collapse any panels that need it.
private void tmrExpand_Tick(object sender, EventArgs e)
{
// Determines whether we need more adjustments.
bool not_done = false;
for (int i = 0; i < ExpandPanels.Length; i++)
{
// See if this panel needs adjustment.
if (ExpandStates[i] == ExpandState.Expanding)
{
// Expand.
Panel pan = ExpandPanels[i];
int new_height = pan.Height + ExpansionPerTick;
if (new_height >= pan.MaximumSize.Height)
{
// This one is done.
new_height = pan.MaximumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
else if (ExpandStates[i] == ExpandState.Collapsing)
{
// Collapse.
Panel pan = ExpandPanels[i];
int new_height = pan.Height - ExpansionPerTick;
if (new_height <= pan.MinimumSize.Height)
{
// This one is done.
new_height = pan.MinimumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
}
// If we are done, disable the timer.
tmrExpand.Enabled = not_done;
}
I want to get a result similar to this - Bootstrap Menu Accordion:
 
Imitate that operation panels expand according to the quantity of item that it contains as long as it does not protrude from the screen, in which case it will show the scroll bar. I know there are software that provide custom controls like DVexpress, DotNetBar Suite among others, but they are Licensed Software I do not want to use it illegally pirate. Can you help me optimize it or create it in another way?
Environment: Visual Studio 2010 & .NET NetFramework 4.
The original question I made it in StackOverFlow in Spanish.
Modulo (Module)
Menu Principal (Main menu)
Mantenimientos (Maintenance)
Procesos (Processes)
Consultas (Queries)
Reportes (Reports)
Note: If someone speaks Spanish and English and can do a better translation, please edit the question. (Excuse the advertising on the image, I recorded the screen with a software trial version).

Drag and Drop custom controls between cells in a grid in WPF

I've got some custom controls which are dynamically added to a custom grid. These controls can span over several columns and rows(which are all the same size). I'd like to drag and drop between the rows and columns. I can drag the individual controls, but they can move anywhere without limit. Even off the grid. I'd like to do it so it can only be dragged inside the grid AND snaps to the column/row it's dragged to.
Is there any easy-ish way to do this?
Honestly, if I could get the current row/column that it's over, then all I'd need to do is set the column/row of it to them and that would probably do it and then just worry about keeping it inside the grid.
I figured out a nice and fun way!
I worked out the position on the grid that the the mouse is on on the MouseUp event and then the relative position of the mouse on the control since it spans several rows/columns.
public void getPosition(UIElement element, out int col, out int row)
{
DControl control = parent as DControl;
var point = Mouse.GetPosition(element);
row = 0;
col = 0;
double accumulatedHeight = 0.0;
double accumulatedWidth = 0.0;
// calc row mouse was over
foreach (var rowDefinition in control.RowDefinitions)
{
accumulatedHeight += rowDefinition.ActualHeight;
if (accumulatedHeight >= point.Y)
break;
row++;
}
// calc col mouse was over
foreach (var columnDefinition in control.ColumnDefinitions)
{
accumulatedWidth += columnDefinition.ActualWidth;
if (accumulatedWidth >= point.X)
break;
col++;
}
}
I then take away the relative positions from the normal positions so that when you drop it, it always drops on the top left of the screen. When I move my controls, I use margins to move it, which screws up the position on the grid at the time, as shown below:
void Chart_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (IsMouseCaptured)
{
Point mouseDelta = Mouse.GetPosition(this);
mouseDelta.Offset(-mouseOffset.X, -mouseOffset.Y);
Margin = new Thickness(
Margin.Left + mouseDelta.X,
Margin.Top + mouseDelta.Y,
Margin.Right - mouseDelta.X,
Margin.Bottom - mouseDelta.Y);
}
}
void Chart_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
mouseOffset = Mouse.GetPosition(this);
CaptureMouse();
parent.currentObject = this;
}
To tackle this, I simply reset the margin.
public void updatePosition()
{
Grid.SetRow(this, (int)position.Y);
Grid.SetColumn(this, (int)position.X);
Margin = new Thickness();
}
I hope this helps someone else since it was rather frustrating for me to find the answer and in the end I managed to get lots of little fragments of how to do things and eventually came up with my own solution.
Is there any easy-ish way to do this?
I'd say that the answer to this question very much depends on your experience using Drag and Drop functionality... for a beginner, I'd say that the answer to this was no, but for someone with some experience and some common sense, it might not be too bad.
To determine which Grid cell the user's mouse is over will not be straight forward. You can handle the PreviewDragOver event and use the VisualTreeHelper.HitTest method to check which control the mouse is currently over:
private void PreviewDragOver(object sender, DragEventArgs e)
{
HitTestResult hitTestResult = VisualTreeHelper.HitTest(adornedUIElement,
e.GetPosition(adornedUIElement));
Control controlUnderMouse = hitTestResult.VisualHit.GetParentOfType<Control>();
}
The GetParentOfType method is a useful extension method that I created, but you can convert it to a normal method easily enough:
public static T GetParentOfType<T>(this DependencyObject element) where T : DependencyObject
{
Type type = typeof(T);
if (element == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null && ((FrameworkElement)element).Parent is DependencyObject) parent = ((FrameworkElement)element).Parent;
if (parent == null) return null;
else if (parent.GetType() == type || parent.GetType().IsSubclassOf(type)) return parent as T;
return GetParentOfType<T>(parent);
}
Of course, once you have a Control in your controlUnderMouse variable, you'll still have some considerable work to do as you work your way through the UIElements until you get to the Grid... you can of course make further use of the GetParentOfType method to make your job easier.

ICSharpCode.TextEditor Vertical Scrolling

Is it possible to configure vertical scrolling in ICSharpCode.TextEditor such that by default no vertical scrollbar is visible. And that only when someone types a lot of lines (beyond current height of this control) that a vertical scrollbar appears automatically. If yes, how?
Its easy to add the function yourself:
1) Goto the namespace ICSharpCode.TextEditor and open the TextAreaControl class. The file location is: C:...\ICSharpCode.TextEditor\Project\Src\Gui\TextAreaControl.cs
2) Add a method to set the visibility of the Horizontal or Vertical scrollbar:
public void ShowScrollBars(Orientation orientation,bool isVisible)
{
if (orientation == Orientation.Vertical)
{
vScrollBar.Visible = isVisible;
}
else
{
hScrollBar.Visible = isVisible;
}
}
3) In the project with the TextEditor, this is how you call the ShowScrollBars() method:
editor.ActiveTextAreaControl.ShowScrollBars(Orientation.Vertical,false);
This code does the trick to show the vertical scrollbar based on the number of text lines:
public TextEditorForm()
{
InitializeComponent();
AddNewTextEditor("New file");
SetSyntaxHighlighting("Mathematica");
editor.ActiveTextAreaControl.TextEditorProperties.IndentationSize = 0;
editor.ActiveTextAreaControl.ShowScrollBars(Orientation.Vertical,false);
editor.TextChanged += new EventHandler(editor_TextChanged);
}
void editor_TextChanged(object sender, EventArgs e)
{
bool isVisible = (editor.ActiveTextAreaControl.GetTotalNumberOfLines > editor.ActiveTextAreaControl.TextArea.TextView.VisibleLineCount);
editor.ActiveTextAreaControl.ShowScrollBars(Orientation.Vertical, isVisible);
}
In the TextAreaControl:
public int GetTotalNumberOfLines()
{
return this.Document.TotalNumberOfLines;
}
ps I'm using this Code Project ICSharpCode-TextEditor project.

Repaint of panel

My problem is that I have a panel in panel. Inside i have the AutoScroll property set to true. When I open a new window this panel is scrolled to begining.
I do that, I save the position before open new window, and I set it after close it. It works but it jumps to the beginning and then back to my position.
The AutoScrollPosition property is a bit funny. When you read it, it will return the current scroll offset, but when you assign it you will need to invert the values:
private static Point GetAutoScrollPosition(Panel panel)
{
return panel.AutoScrollPosition;
}
private static void SetAutoScrollPosition(Panel panel, Point position)
{
panel.AutoScrollPosition = new Point(-position.X, -position.Y);
}
Now you can retrieve the current position and set it like so:
Point pos = GetAutoScrollPosition(myPanel);
SetAutoScrollPosition(myPanel, pos);
Have you tried setting autoscroll to false?
I do something lik You wrote
_scrollPozition = -(pnlMain.AutoScrollPosition.Y);
DialogResult result = MessageBox.Show("Delete: ", MessageBoxButtons.YesNo);
dgvClendar.Focus();
private void pnlMain_Paint(object sender, PaintEventArgs e)
{
if (pnlMain.AutoScrollPosition.Y == 0)
{
pnlMain.AutoScrollPosition = new Point(0, _scrollPozition);
_scrollPozition = 0;
}
}
on paint it is set, but if you look everything is moved for a moment. I need to block this scroll to begin, or block painting, and repaint after scroll to current position.

Categories