I have a UserControl within a WinForms-form where within is a panel that resizes its content by custom code when the parent window is resized:
private void ResizeElements(FlowLayoutPanel panel) {
panel.SuspendLayout();
foreach (Control ctrl in panel.Controls) {
ctrl.Width = panel.ClientSize.Width - 20;
}
panel.ResumeLayout();
}
private void flowPanel_Resize(object sender, EventArgs e) {
ResizeElements((FlowLayoutPanel)sender);
}
This works just fine when I resize the window manually by grabbing it at the lower right corner and resize there:
This does not work however, wenn I maximize the window and after that click onto the restore-controlbox between minimize and maximize.
The controls from that Panel seem to have the right width, but the FlowLayoutPanel that contains them has a horizontal scrollbar:
When I resize the window manually again the horizontal scrollbar disappears and everything is as expected.
The flowpanel has DockingStyle.Fill
Update: I currently do the following to get around this issue:
private void frmMain_Resize(object sender, EventArgs e)
{
var oldWindowState = _previousWindowState;
_previousWindowState = this.WindowState;
if (oldWindowState == FormWindowState.Maximized && this.WindowState == FormWindowState.Normal)
{
this.Width++;
this.Width--;
}
}
So I resize manually when detecting a restore-click forcing the correct events to be fired. I am fully aware that this is an evil hack and still would prefer a more clean solution.
(Update): Parent Controls:
WinForm
->Panel Dock: Fill, AutoSize: false
->UserControl Dock: Fill, AutoSize: false
->TabControl Dock: Fill
->Tab
->FlowLayoutPanel Dock: Fill, AutoSize: false
Related
I have TabControl that has a DataGrid inside each TabItem. It's all populated via binding. I use the row details expansion functionality, so have set the VirtualizingPanel ScrollUnit to Pixel, so scrolling is a bit more natural.
When changing between TabItems I have row selection behaving correctly. However, setting the vertical offset on the DataGrid's ScrollViewer so it is in the exact same position as it was when you left the TabItem is not working correctly.
The way it works at the moment is, I have a behavior class on the DataGrid. On a Scrollviewer ScrollChangedEvent it saves the VerticalOffset. Upon changing to a new TabItem and changing back to the original TabItem, in the DataGrid's DataContextChanged event I set the ScrollViewer's VerticalOffset to the saved VerticalOffset.
public class DataGridBehaviors : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.DataContextChanged += DataGrid_DataContextChanged;
this.AssociatedObject.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(DataGridScrollViewer_ScrollChanged));
}
protected override void OnDetaching()
{
Console.WriteLine("OnDetaching");
this.AssociatedObject.RemoveHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(DataGridScrollViewer_ScrollChanged));
this.AssociatedObject.DataContextChanged -= DataGrid_DataContextChanged;
base.OnDetaching();
}
private void DataGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ModuleGeometry oldModuleGeometry = (ModuleGeometry)e.OldValue;
ModuleGeometry newModuleGeometry = (ModuleGeometry)e.NewValue;
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(this.AssociatedObject);
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset(newModuleGeometry.VerticalScrollPosition);
}
}
private void DataGridScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ModuleGeometry modGeom = (ModuleGeometry)this.AssociatedObject.DataContext;
modGeom.VerticalScrollPosition = e.VerticalOffset;
}
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
}
What is happening is you scroll down and the top DataGridRow is partially displayed (You only see half of it). Then when you toggle between two TabItems, the program sets the VerticalOffset correctly, but then it resets again automatically to the top of the partially displayed row (showing it fully).
Before toggling. Saves VertcialOffset to 4327.2
After toggling back to the original TabItem Sets VerticalOffset to 4327.2, then for some reason and somehow automatically resets VerticalOffset to 4321.5, which is the top of the 'previously' partially visible row
It gets even weirder when you have an expanded row loaded in the VirtualizingPanel, the jump is more dramatic.
Before toggling
After toggling back to the original TabItem
I would like to see the scroll position in exactly the same spot as when I left it, how can I accomplish this?
The easiest approach would probably be to stop the TabControl from unloading the visual tree when you switch tabs. Then the content of each tab should be preserved.
Please refer to the following links for more information about this.
How to stop Wpf Tabcontrol to unload Visual tree on Tab change
WPF - Elements inside DataTemplate property issue when no binding?
I'm developing a windows forms application. And i have the following issue: in a form there's a panel, and in that panel i have a number of controls (just a label with a text box, the number is determined at runtime). This panel has a size that is smaller than the sum of all the controls added dynamically. So, i need a scroll. Well, the idea is: when the user open the form: the first of the controls must be focused, the the user enters a text and press enter, the the next control must be focused, and so until finished.
Well it's very probably that not all the controls fit in the panel, so i want that when a control inside the panel got focus the panel scrolls to let the user see the control and allow him to see what he is entering in the text box.
I hope to be clear.
here is some code, this code is used to generated the controls and added to the panel:
List<String> titles = this.BancaService.ValuesTitle();
int position = 0;
foreach (String title in titles)
{
BancaInputControl control = new BancaInputControl(title);
control.OnInputGotFocus = (c) => {
//pnBancaInputContainer.VerticalScroll.Value = 40;
//pnBancaInputContainer.AutoScrollOffset = new Point(0, c.Top);
// HERE, WHAT CAN I DO?
};
control.Top = position;
this.pnBancaInputContainer.Controls.Add(control);
position += 10 + control.Height;
}
If you set AutoScroll to true this will be taken care of automatically. As for the idea that Enter should move focus to next field, the best solution would be to execute enter as if it was TAB key in BancaInputControl:
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.Enter)
{
e.Handled = true;
// Move focus to next control in parent container
Parent.SelectNextControl(this, true, true, false, false);
}
}
If BancaInputControl is composite control (a UserControl containing other controls) each child control should hook up KeyDown event to this handler. It tries to move focus to next control in BancaInputControl; if it fails, moves focus to parent container's next control.
private void textBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.Handled = true;
if (!SelectNextControl((Control)sender, true, true, false, false))
{
Parent.SelectNextControl(this, true, true, false, false);
}
}
}
I have MSChart control within Panel in my form. Panel has AutoScroll property set to True. Once the chart gets too big - user is able to scroll through it.
Once user presses Ctrl+MouseWheel I am enabling zoom in/out to the chart area. Currently on MouseWheel - both Panel and Chart are scrolling. How do I disable Panel scrollbars at the time when Chart is handling scrolling events?
After disabling AutoScroll property Panel is moving scroll to the top and hiding vertical scrollbar - so this is not what I want:
void Chart_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control == true)
{
(Parent as Panel).AutoScroll = false;
}
}
void Chart_KeyUp(object sender, KeyEventArgs e)
{
if (e.Control == false)
{
(Parent as Panel).AutoScroll = true;
}
}
I would like Panel scrollbar to "freeze" when chart is zooming (ctrl+mousewheel) and activate when chart is idle (mousewheel). Any ideas?
Here is the situation:
I am trying to control flowLayoutControl's scroll bar from devexpress controls VerticalScroll.
Now - flowLayoutControl with autoscroll = true. I added a new verticalscroll control and dock it to Right. So now the Devexpress Vertical Scroll control is right on top of FlowLayout scrollbar.
Also the FlowLayoutPanel vertical scroll does not hide when following code is run:
spotWinFlowLayout1.VerticalScroll.Visible = false
I have setup the following event handlers:
private void spotWinFlowLayout1_Resize(object sender, EventArgs e)
{
SetupVerticalScrollBar();
}
private void SetupVerticalScrollBar()
{
vScrollBar1.Minimum = spotWinFlowLayout1.VerticalScroll.Minimum;
vScrollBar1.Maximum = spotWinFlowLayout1.VerticalScroll.Maximum;
vScrollBar1.LargeChange = spotWinFlowLayout1.VerticalScroll.LargeChange;
vScrollBar1.SmallChange = spotWinFlowLayout1.VerticalScroll.SmallChange;
}
private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
spotWinFlowLayout1.VerticalScroll.Value = e.NewValue;
}
Everything is working fine except when on form Load there is already a scrollbar on flowLayoutControl,
spotWinFlowLayout1.VerticalScroll.XXX properties not set yet. So both the scrollbars are out of Sync. But as soon as I resize the form both get Sync.
So when is scrollbar for the FlowLayoutPanel initialized?
So when is scrollbar for the
FlowLayoutPanel initialized?
This might sound like a smartalec answer: "When the control is drawn or placed on the form" which is the reason when you adjust the size of the form they are in sync.( they are being drawn again )
The solution is to manually add the scrollbars yourself.
Is there a way to detect if the scrollbar from the ScrollViewer in a ListView has reached the bottom of the virtual scroll space? I would like to detect this to fetch more items from the server to put into the bound ObservableCollection on the ListView.
Right now I'm doing this:
private void currentTagNotContactsList_scrollChanged(object sender, ScrollChangedEventArgs e) {
ListView v = (ListView)sender;
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight) {
Debug.Print("At the bottom of the list!");
}
}
Is this even correct? I also need to differentiate between the vertical scrollbar causing the event and the horizontal scrollbar causing it (i.e. I don't want to keep generating calls to the server if you scroll horizontally at the bottom of the box).
Thanks.
//A small change in the "Max's" answer to stop the repeatedly call.
//this line to stop the repeatedly call
ScrollViewer.CanContentScroll="False"
private void dtGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
//this is for vertical check & will avoid the call at the load time (first time)
if (e.VerticalChange > 0)
{
if (e.VerticalOffset + e.ViewportHeight == e.ExtentHeight)
{
// Do your Stuff
}
}
}
I figured it out. It seems I should have been getting events from the ScrollBar (<ListView ScrollBar.Scroll="currentTagNotContactsList_Scroll" in XAML) itself, rather than the viewer. This works, but I just have to figure a way to avoid the event handler being called repeatedly once the scrollbar is down. Maybe a timer would be good:
private void currentTagNotContactsList_Scroll(object sender, ScrollEventArgs e) {
ScrollBar sb = e.OriginalSource as ScrollBar;
if (sb.Orientation == Orientation.Horizontal)
return;
if (sb.Value == sb.Maximum) {
Debug.Print("At the bottom of the list!");
}
}
For UWP I got it like this
<ScrollViewer Name="scroll" ViewChanged="scroll_ViewChanged">
<ListView />
</ScrollViewer>
private void scroll_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
btnNewUpdates.Visibility = Visibility.Visible;
}
you can try this way:
<ListView ScrollViewer.ScrollChanged="Scroll_ScrollChanged">
and in Back:
private void Scroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// Get the border of the listview (first child of a listview)
Decorator border = VisualTreeHelper.GetChild(sender as ListView, 0) as Decorator;
// Get scrollviewer
ScrollViewer scrollViewer = border.Child as ScrollViewer;
if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
Debug.Print("At the bottom of the list!");
}
Not recommand to use ScrollBar.Scroll , beacause if you scroll the middle wheel of the mouse, it won't work.
ScrollBar.Scroll="currentTagNotContactsList_Scroll"
The following support both right side scroll bar and mouse's wheel scroll.
in listbox's xmal:
ScrollViewer.ScrollChanged="ScrollViewer_ScrollChanged"
in c#:
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var listBox = (ListBox)sender;
var scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(listBox, 0);
if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
{
Console.WriteLine("____At the bottom of the list!");
}
}