WPF Scrollviewer shrink height, bring element into view - c#

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
}

Related

Is it possible to create Windows 10 desktop apps with list boxes that scroll smoothly like they do on macOS? [duplicate]

Is it possible to implement smooth scroll in a WPF listview like how it works in Firefox?
When the Firefox browser contained all listview items and you hold down the middle mouse button (but not release), and drag it, it should smoothly scroll the listview items. When you release it should stop.
It looks like this is not possible in winforms, but I am wondering if it is available in WPF?
You can achieve smooth scrolling but you lose item virtualisation, so basically you should use this technique only if you have few elements in the list:
Info here: Smooth scrolling on listbox
Have you tried setting:
ScrollViewer.CanContentScroll="False"
on the list box?
This way the scrolling is handled by the panel rather than the listBox... You lose virtualisation if you do that though so it could be slower if you have a lot of content.
It is indeed possible to do what you're asking, though it will require a fair amount of custom code.
Normally in WPF a ScrollViewer uses what is known as Logical Scrolling, which means it's going to scroll item by item instead of by an offset amount. The other answers cover some of the ways you can change the Logical Scrolling behavior into that of Physical Scrolling. The other way is to make use of the ScrollToVertialOffset and ScrollToHorizontalOffset methods exposed by both ScrollViwer and IScrollInfo.
To implement the larger part, the scrolling when the mouse wheel is pressed, we will need to make use of the MouseDown and MouseMove events.
<ListView x:Name="uiListView"
Mouse.MouseDown="OnListViewMouseDown"
Mouse.MouseMove="OnListViewMouseMove"
ScrollViewer.CanContentScroll="False">
....
</ListView>
In the MouseDown, we are going to record the current mouse position, which we will use as a relative point to determine which direction we scroll in. In the mouse move, we are going to get the ScrollViwer component of the ListView and then Scroll it accordingly.
private Point myMousePlacementPoint;
private void OnListViewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
myMousePlacementPoint = this.PointToScreen(Mouse.GetPosition(this));
}
}
private void OnListViewMouseMove(object sender, MouseEventArgs e)
{
ScrollViewer scrollViewer = ScrollHelper.GetScrollViewer(uiListView) as ScrollViewer;
if (e.MiddleButton == MouseButtonState.Pressed)
{
var currentPoint = this.PointToScreen(Mouse.GetPosition(this));
if (currentPoint.Y < myMousePlacementPoint.Y)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - 3);
}
else if (currentPoint.Y > myMousePlacementPoint.Y)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + 3);
}
if (currentPoint.X < myMousePlacementPoint.X)
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - 3);
}
else if (currentPoint.X > myMousePlacementPoint.X)
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + 3);
}
}
}
public static DependencyObject GetScrollViewer(DependencyObject o)
{
// Return the DependencyObject if it is a ScrollViewer
if (o is ScrollViewer)
{ return o; }
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
{
var child = VisualTreeHelper.GetChild(o, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
There's some areas it's lacking as it's just a proof of concept but it should definitely get you started in the right direction. To have it constantly scroll once the mouse is moved away from the initial MouseDown point, the scrolling logic could go into a DispatcherTimer or something similar.
Try setting the ScrollViewer.CanContentScroll attached property to false on the ListView. But like Pop Catalin said, you lose item virtualization, meaning all the items in the list get loaded and populated at once, not when a set of items are needed to be displayed - so if the list is huge, it could cause some memory and performance issues.
try setting the listview's height as auto and wrapping it in a scroll viewer.
<ScrollViewer IsTabStop="True" VerticalScrollBarVisibility="Auto">
<ListView></ListView>
</ScrollViewer>
Don't forget to mention the height of ScrollViewer
Hope this helps....
I know this post is 13 years old, but this is still something people want to do.
in newer versions of .Net you can set VirtualizingPanel.ScrollUnit="Pixel"
this way you won't lose virtualization and you get scroll per pixel instead of per item.

C# WinForms: Make panel scrollbar invisible

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.

Dynamically filter and resize context menu c# while keeping its gravity to bottom

I have a system tray context menu with 26 items and an additional ToolStripTextBox menu item. When the user enters text to the filter textbox it continuously filters the menu items as the user types and hides categories on the fly by setting the ToolStripMenuItem Visible property to false.
It's working!
The issue is that when it gets filtered, the height of the context menu gets shorter from the bottom towards the top. The origin point for the menu is the upper right corner, causing it to shrink upwards. Since it is a system tray related context menu, I expect it to shrink downwards (bottom gravity).
How to make this happen?
Still not sure if there is a "proper" built-in method to do this...
In the mean-time, here's a hack that changes the Bounds() of the ContextMenuStrip whenever the size changes. It simply shifts the ContextMenuStrip down/up by however much the height changed. I've wired up the Opened() and SizeChanged() events of my ContextMenuStrip and store the last Bounds() in the "lastBounds" variable at class level:
private Rectangle lastBounds;
private void contextMenuStrip1_Opened(object sender, EventArgs e)
{
lastBounds = contextMenuStrip1.Bounds;
}
private void contextMenuStrip1_SizeChanged(object sender, EventArgs e)
{
Rectangle rc = contextMenuStrip1.Bounds;
int diff = lastBounds.Height - rc.Height;
if (diff > 0)
{
contextMenuStrip1.Bounds = new Rectangle(new Point(rc.X, rc.Y + diff), rc.Size);
lastBounds = contextMenuStrip1.Bounds;
}
else
{
contextMenuStrip1.Bounds = new Rectangle(new Point(rc.X, rc.Y - diff), rc.Size);
lastBounds = contextMenuStrip1.Bounds;
}
}

Get width of element of auto width

I am trying to get the Width of a StackPanel. I tried:
double width = stk_main.ActualWidth;
which gave zero and it shouldnt be. Also:
double width = stk_main.Width;
which gives NaN because width set to auto previously. So how can I get the width?
You can use sizeChanged, but it doesn't work when the stack panel you used not change size.
private void stk_main_SizeChanged(object sender, SizeChangedEventArgs e)
{
double yourWidthNow = e.NewSize.Width;
}
Hope it can give you some help...
You could attach to the Loaded event of the StackPanel as the layout pass will have completed by this point and the ActualWidth will be set.

C# using the scrollbar control / event (without textbox or window scroll)

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.

Categories

Resources