WPF TextBox Scrollbars actually visible or not? - wpf

I have a TextBox in a WPF Project whose verticalscrollbar visibility is set to Auto. When I set a text in it at Runtime the vertical scroll bar becomes visible when text is more and vertical scroll bar hides when text is less.
What i want is if when i assign a large text to it (and verticalscrollbar becomes visible) then i will run a recursive loop which will reduce font size to a level till the scroll bar become hidden. I want to get the actual visibility value of verticalscrollbar.
In code behind the verticalscrollbarvisibility property always gives auto. (i think boz its set to auto in XAML).
// If my approach is wrong for this problem please let me know that also.

I found it. I passed my textbox in the function
ScrollViewer sv = FindVisualChild<ScrollViewer>(mytextbox);
if (sv != null)
{
// do something with ScrollViewer
}
public static T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
It returns me ScrollViewer whose visibility property is what i wanted.

Related

WPF Scrollviewer scroll to child element not working

Here is what I am trying to do. I have a Scrollviewer and nested inside it are UserControls, nested in other UserControls, inside grids, stackpanels and other containers. When I click a button that adds yet another child somewhere within this hierarchy I would like to scroll to see said child. Code of function below:
public static void ScrollParentNamedScrollViewerDown(DependencyObject child, string strTargetParent, DependencyObject newStartPoint = null)
{
if(child == null) return;
if(newStartPoint == null) newStartPoint = child;
ScrollViewer scvPotentialTarget = GetParentOfType<ScrollViewer>(newStartPoint);
if (scvPotentialTarget == null) return;
if (scvPotentialTarget.Name != strTargetParent)
{
ScrollParentNamedScrollViewerDown(child, strTargetParent, scvPotentialTarget);
}
else
{
UIElement scrollTarget = child as UIElement;
if (scrollTarget == null)
scvPotentialTarget.ScrollToBottom();
else{
Point pTarget = scrollTarget.TranslatePoint(new Point(0, 0), scvPotentialTarget);
if (pTarget == null)
scvPotentialTarget.ScrollToBottom();
else
scvPotentialTarget.ScrollToVerticalOffset(pTarget.Y);
}
}
This function is used like this:
gbSubWindow.Visibility = System.Windows.Visibility.Visible;
gbSubWindow.Content = uc;
ScrollParentNamedScrollViewerDown(gbSubWindow, "OmsWindowScrollViewer");
where gbSubWindow was a previously empty, hidden expander at the bottom of a nested UserControl
For some reason I am getting ridiculously low values for the vertical offset when I execute the TranslatePoint function - my scrollviewer scrollheight is near 800, and I am adding a child that will show up at the very bottom beneath 2 large child control and I am getting values in 160ish range.
Anyone have any ideas, what is happening here?
In case that is the answer
BringIntoView on the control
Since UserControl derives from FrameWorkElelement it should work.
I have only used it for ListItem
FrameworkElement.BringIntoView Method

Get the scroll position of a WPF TextBox

I need to add some decoration to the contents of a WPF TextBox control. That works fine basically, I can get the position of specified character indices and layout my other elements accordingly. But it all breaks when the TextBox is scrolled. My layout positions don't match with the displayed text anymore because it has moved elsewhere.
Now I'm pretty surprised that the TextBox class doesn't provide any information about its scrolling state, nor any events when the scrolling has changed. What can I do now?
I used Snoop to find out whether there is some scrolling sub-element that I could ask, but the ScrollContentPresenter also doesn't have any scrolling information available. I'd really like to put my decoration elements right into the scrolled area so that the scrolling can affect them, too, but there can only be a single content control and that's one of the TextBox internals already.
I'm not sure how to capture an event when the textbox has been scrolled (probably use narohi's answer for that), but there is a simple way to see what the current scroll position is:
// Gets or sets the vertical scroll position.
textBox.VerticalOffset
(From http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.textboxbase.verticaloffset(v=vs.100).aspx)
I'm using it to see if the textbox is scrolled to the end, like this:
public static bool IsScrolledToEnd(this TextBox textBox)
{
return textBox.VerticalOffset + textBox.ViewportHeight == textBox.ExtentHeight;
}
You can get the ScrollViewer with this method by passing in your textbox as the argument and the type ScrollView. Then you may subscribe to the ScrollChanged event.
public static T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
{
if (obj == null) return default(T);
int numberChildren = VisualTreeHelper.GetChildrenCount(obj);
if (numberChildren == 0) return default(T);
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)(object)child;
}
}
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
var potentialMatch = FindDescendant<T>(child);
if (potentialMatch != default(T))
{
return potentialMatch;
}
}
return default(T);
}
Example:
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer s = FindDescendant<ScrollViewer>(txtYourTextBox);
s.ScrollChanged += new ScrollChangedEventHandler(s_ScrollChanged);
}
void s_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// check event args for information needed
}

Dynamic spacing between controls in the Toolbar in WPF

I have a toolbar which contains several controls, say 10.
I want to place last three controls in the right corner of the tool bar with some space between these three controls and remaining controls.
When I resize the window, it should reduce the space, once there is no space it should be cliped to overflow drop down one by one.
Note: I need to use only one toolbar.
I did this once before
The items existed in a customized ScrollViewer so they could go off-screen, and the SizeChanged event of the ScrollViewer would check each item to see if it was off-screen or not. If it wasn't visibile, or was partially visible, then the item would get hidden and the data object would get added to a Menu. If the item was visible, it would remove it from the Menu if it existed there, and set it visible. The Menu was only visible if it had items.
This was done a while back when I was still new to WPF, so I'm sure the process could be improved on.
The helper class I used to determine item visibility is listed below
public static ControlVisibility IsObjectVisibleInContainer(FrameworkElement child, UIElement parent)
{
GeneralTransform childTransform = child.TransformToAncestor(parent);
Rect childSize = childTransform.TransformBounds(new Rect(new Point(0, 0), new Point(child.Width, child.Height)));
Rect result = Rect.Intersect(new Rect(new Point(0, 0), parent.RenderSize), childSize);
if (result == Rect.Empty)
{
return ControlVisibility.Hidden;
}
else if (result.Height == childSize.Height && result.Width == childSize.Width)
{
return ControlVisibility.Full;
}
else if (result.Height == childSize.Height)
{
return ControlVisibility.FullHeightPartialWidth;
}
else if (result.Width == childSize.Width)
{
return ControlVisibility.FullWidthPartialHeight;
}
else
{
return ControlVisibility.Partial;
}
}
public enum ControlVisibility
{
Hidden,
Partial,
Full,
FullHeightPartialWidth,
FullWidthPartialHeight
}
It could be used like this:
ControlVisibility itemVisibility =
MyHelpers.IsObjectVisibleInContainer(someItem, parentContainer);

First Listbox scrollbar movement should effect Second Listbox scrollbar movement? But How?

I have 4 List Boxes in my WPF App. Each of them, at any given point of time contains equal no. of String ListBoxItems in them. If selected index of any one of them changes the other three also reflect the same behaviour. What i want is that when a user moves scrollbar of one of them the other three should also move simultaneoulsly.
I tried Scrollintoview but it does not work bcoz if i select an item of a listBox and apply scrollintoview for other three Listboxes the selected item in them come on the top.
That's why i think scrollbar movement should be the best option for this. How to do that?
In XAML hook the ScrollChanged event
ScrollViewer.ScrollChanged="ListBox_ScrollChanged"
In CodeBehind find the Scrollviewers inside the ListBoxes and apply the Vertical offset:
private void ListBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var sourceScrollViewer = FindVisualChild<ScrollViewer>(sender as DependencyObject) as ScrollViewer;
var targetScrollViewer = FindVisualChild<ScrollViewer>(listBox2) as ScrollViewer;
targetScrollViewer.ScrollToVerticalOffset(sourceScrollViewer.VerticalOffset);
}
// helper method
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Well in code it is something like that :
1) get the four scrollviewers of the four ListViews
( by finding them within the child (VisualTreeHelper.getchild)
inside a method like FindDescendant(...))
2) hook their scrolls events (ScrollChanged) to a common sub that
will get the VerticalOffset of the one that triggered the event
and ScrollToVerticalOffset(.) the others.
must be possible in xaml also, but seems more complicated to me.

Record items visible to user in ListBox

I have a ListBox or DataGrid filled with thousands of entries. I would like to know items that the user has looked at (scrolling, searching or otherwise). How can I tell what is visible to the user in the ListBox?
Bonus: Set a timer so that the item has to be shown for a minimum of N milliseconds (in the event the user is just pulling down the scrollbar).
Update: This is a near duplicate of Get items in view within a listbox - but the solution it gives, using "SelectedItems", is not sufficient. I need to know the items whether they are selected or not!
All you need to do is to get the underlying StackPanel that's inside the ListBox. It has enough information about which elements are showing. (It implements the interface IScrollInfo).
To get the underlying StackPanel (or actually VirtualizingStackPanel) from a given ListBox, we'll have to use VisualTreeHelper to go through the Visual Tree and look for the VirtualizingStackPanel, like so:
private VirtualizingStackPanel GetInnerStackPanel(FrameworkElement element)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;
if (child == null) continue;
Debug.WriteLine(child.ToString());
if (child is VirtualizingStackPanel) return child as VirtualizingStackPanel;
var panel = GetInnerStackPanel(child);
if (panel != null)
return panel;
}
return null;
}
Now that we have the StackPanel, we're very close. The StackPanel has the properties VerticalOffset and ViewportHeight (both coming from IScrollInfo) that can give us all the information we need.
private void button1_Click(object sender, RoutedEventArgs e)
{
var theStackPanel = GetInnerStackPanel(MyListBox);
List<FrameworkElement> visibleElements = new List<FrameworkElement>();
for (int i = 0; i < theStackPanel.Children.Count; i++)
{
if (i >= theStackPanel.VerticalOffset && i <= theStackPanel.VerticalOffset + theStackPanel.ViewportHeight)
{
visibleElements.Add(theStackPanel.Children[i] as FrameworkElement);
}
}
MessageBox.Show(visibleElements.Count.ToString());
MessageBox.Show(theStackPanel.VerticalOffset.ToString());
MessageBox.Show((theStackPanel.VerticalOffset + theStackPanel.ViewportHeight).ToString());
}

Resources