ScrollViewer inside ScrollViewer WPF - wpf

In my WPF page I need to scroll vertically and inside (this ScrollViewer) I need several horizontal scrolls. At the beginning i had the problem was that when I pointed with my mouse on the inside ScrollViewer area I cant scroll the page (vertically).I found a post:
https://serialseb.com/blog/2007/09/03/wpf-tips-6-preventing-scrollviewer-from/
it resolved my mouse problem, but now I have the same problem on Touch display. I can't use my finger to vertically scroll on the inside scrollviewer. Anyone has any idea to change the below code to let it work for touch display? Thanks
public class ScrollViewerCorrector
{
public static bool GetFixScrolling(DependencyObject obj)
{
return (bool)obj.GetValue(FixScrollingProperty);
}
public static void SetFixScrolling(DependencyObject obj, bool value)
{
obj.SetValue(FixScrollingProperty, value);
}
public static readonly DependencyProperty FixScrollingProperty =
DependencyProperty.RegisterAttached("FixScrolling", typeof(bool), typeof(ScrollViewerCorrector), new FrameworkPropertyMetadata(false, ScrollViewerCorrector.OnFixScrollingPropertyChanged));
public static void OnFixScrollingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ScrollViewer viewer = sender as ScrollViewer;
if (viewer == null)
throw new ArgumentException("The dependency property can only be attached to a ScrollViewer", "sender");
if ((bool)e.NewValue == true)
viewer.PreviewMouseWheel += HandlePreviewMouseWheel;
else if ((bool)e.NewValue == false)
viewer.PreviewMouseWheel -= HandlePreviewMouseWheel;
}
private static List<MouseWheelEventArgs> _reentrantList = new List<MouseWheelEventArgs>();
private static void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollControl = sender as ScrollViewer;
if (!e.Handled && sender != null && !_reentrantList.Contains(e))
{
var previewEventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.PreviewMouseWheelEvent,
Source = sender
};
var originalSource = e.OriginalSource as UIElement;
_reentrantList.Add(previewEventArg);
originalSource.RaiseEvent(previewEventArg);
_reentrantList.Remove(previewEventArg);
// at this point if no one else handled the event in our children, we do our job
if (!previewEventArg.Handled && ((e.Delta > 0 && scrollControl.VerticalOffset == 0)
|| (e.Delta <= 0 && scrollControl.VerticalOffset >= scrollControl.ExtentHeight - scrollControl.ViewportHeight)))
{
e.Handled = true;
var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
eventArg.RoutedEvent = UIElement.MouseWheelEvent;
eventArg.Source = sender;
var parent = (UIElement)((FrameworkElement)sender).Parent;
parent.RaiseEvent(eventArg);
}
}
}
}

Related

DependencyPropertyListener() for WPF

I would like to operate a project planned for windows phone to be used in WPF.
Here is the link :
Loading Data when the User Scrolls to the End
By repeating the code , I perceived, that WPF does not know DependencyPropertyListener() in this method :
static void element_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= element_Loaded;
ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);
if (scrollViewer == null)
{
throw new InvalidOperationException("ScrollViewer not found.");
}
var listener = new DependencyPropertyListener();
listener.Changed
+= delegate
{
bool atBottom = scrollViewer.VerticalOffset
>= scrollViewer.ScrollableHeight;
if (atBottom)
{
var atEnd = GetAtEndCommand(element);
if (atEnd != null)
{
atEnd.Execute(null);
}
}
};
Binding binding = new Binding("VerticalOffset") { Source = scrollViewer };
listener.Attach(scrollViewer, binding);
}
Would there be a way around this object ?
You can listen to dependency property value change using a DependencyPropertyDescriptor.
static void element_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= element_Loaded;
ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);
if (scrollViewer == null)
{
throw new InvalidOperationException("ScrollViewer not found.");
}
var dpd = DependencyPropertyDescriptor.FromProperty(ScrollViewer.VerticalOffsetProperty, typeof(ScrollViewer));
dpd.AddValueChanged(scrollViewer, delegate(object o, EventArgs args)
{
bool atBottom = scrollViewer.VerticalOffset
>= scrollViewer.ScrollableHeight;
if (atBottom)
{
var atEnd = GetAtEndCommand(element);
if (atEnd != null)
{
atEnd.Execute(null);
}
}
});
}

Selecting text and caret in a texbox on focus

public class TextBoxSelectionBehavior : Behavior<TextBox>
{
public static bool GetSelectAllTextOnFocus(TextBox textBox)
{
return (bool)textBox.GetValue(SelectAllTextOnFocusProperty);
}
public static void SetSelectAllTextOnFocus(TextBox textBox, bool value)
{
textBox.SetValue(SelectAllTextOnFocusProperty, value);
}
public static readonly DependencyProperty SelectAllTextOnFocusProperty =
DependencyProperty.RegisterAttached(
"SelectAllTextOnFocus",
typeof(bool),
typeof(TextBoxSelectionBehavior),
new UIPropertyMetadata(false, OnSelectAllTextOnFocusChanged));
private static void OnSelectAllTextOnFocusChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox == null) return;
if (e.NewValue is bool == false) return;
if ((bool)e.NewValue)
{
textBox.GotFocus += SelectAll;
textBox.PreviewMouseDown += IgnoreMouseButton;
}
else
{
textBox.GotFocus -= SelectAll;
textBox.PreviewMouseDown -= IgnoreMouseButton;
}
}
private static void SelectAll(object sender, RoutedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox == null) return;
textBox.SelectAll();
textBox.CaretIndex = textBox.Text.Length;
}
private static void IgnoreMouseButton(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null || textBox.IsKeyboardFocusWithin) return;
textBox.Focus();
e.Handled = true;
}
The textbox in question is an editable cell.
Once the cell is double clicked, i want a selectall of the text as well as a caret blink at the end of the text, on getfocus.
The text is highlighted correctly.
But setting the caret at the end is deselecting the highlighted text.
i cant do both at once. how to accomplish this?
I have tried selection start,length and other approaches.nothing works.

How do I hittest for a TabControl tab?

Given a point relative to a Page, how do I hittest for a TabControl's tab? VisualTreeHelper.HitTest gives me the contents, but when I go up the visual tree I see nothing that would tell me that I have actually hit a tab. I don't even see the tab control itself.
public class ViewManipulationAgent : IDisposable
{
private const int _limit = 125;
private INavigationService _navigationService;
private FrameworkElement _container;
private FrameworkElement _element;
private TranslateTransform _translate;
private IInputElement _touchTarget;
// When I use this object,
// a_container is the main Frame control in my application.
// a_element is a page within that frame.
public ViewManipulationAgent(FrameworkElement a_container, FrameworkElement a_element)
{
_navigationService = a_navigationService;
_container = a_container;
_element = a_element;
// Since I set IsManipulationEnabled to true all touch commands are suspended
// for all commands on the page (a_element) unless I specifically cancel (see below)
_element.IsManipulationEnabled = true;
_element.PreviewTouchDown += OnElementPreviewTouchDown;
_element.ManipulationStarting += OnElementManipulationStarting;
_element.ManipulationDelta += OnElementManipulationDelta;
_element.ManipulationCompleted += OnElementManipulationCompleted;
_translate = new TranslateTransform(0.0, 0.0);
_element.RenderTransform = _translate;
}
// Since the ManipulationStarting doesn't provide position I capture the position
// here and then hit test elements to find any controls for which I want to bypass
// manipulation.
private void OnElementPreviewTouchDown(object sender, TouchEventArgs e)
{
var position = e.GetTouchPoint(_element).Position;
_touchTarget = null;
HitTestResult result = VisualTreeHelper.HitTest(_element, position);
if (result.VisualHit == null)
return;
var button = VisualTreeHelperEx.FindAncestorByType<ButtonBase>(result.VisualHit) as ButtonBase;
if (button != null)
{
_touchTarget = button;
return;
}
var slider = VisualTreeHelperEx.FindAncestorByType<Slider>(result.VisualHit) as Slider;
if (slider != null)
{
_touchTarget = slider;
return;
}
}
// Here is where I cancel manipulation if a specific touch target was found in the
// above event.
private void OnElementManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
if (_touchTarget != null)
{
e.Cancel(); // <- I have to cancel manipulation or the buttons and other
// controls cannot be manipulated by the touch interface.
return;
}
e.ManipulationContainer = _container;
}
private void OnElementManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
var element = e.Source as FrameworkElement;
if (element == null)
return;
var translate = _translate.X + e.DeltaManipulation.Translation.X;
if (translate > _limit)
{
GoBack();
translate = _limit;
}
if (translate < -_limit)
{
GoForward();
translate = -_limit;
}
_translate.X = translate;
}
private void GoForward()
{
var navigationService = ServiceLocator.Current.GetInstance<INavigationService>();
navigationService.GoForward();
}
private void GoBack()
{
var navigationService = ServiceLocator.Current.GetInstance<INavigationService>();
navigationService.GoBack();
}
private void OnElementManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
_touchTarget = null;
_translate.X = 0;
}
public void Dispose()
{
_element.PreviewTouchDown -= OnElementPreviewTouchDown;
_element.ManipulationStarting -= OnElementManipulationStarting;
_element.ManipulationDelta -= OnElementManipulationDelta;
_element.ManipulationCompleted -= OnElementManipulationCompleted;
}
}

TextBox AttachedProperty to Select All text not working as expected?

I have an attached property called "SelectAllOnFocus". Values of true/false.
public static class TextBoxProps
{
private static void MyTextBoxKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
((TextBox)sender).Text = string.Empty;
}
}
public static void SetSelectAllOnFocus(DependencyObject dependencyObject, bool selectAllOnFocus)
{
if (!ReferenceEquals(null, dependencyObject))
{
dependencyObject.SetValue(SelectAllOnFocus, selectAllOnFocus);
}
}
public static bool GetSelectAllOnFocus(DependencyObject dependencyObject)
{
if (!ReferenceEquals(null, dependencyObject))
{
return (bool)dependencyObject.GetValue(SelectAllOnFocus);
}
else
{
return false;
}
}
private static void OnSelectAllOnFocus(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool selectAllOnFocus = (bool)e.NewValue == true;
var theTextBox = d as TextBox;
if (selectAllOnFocus && theTextBox != null)
{
theTextBox.PreviewMouseDown -= MyTextBoxMouseEnter; theTextBox.PreviewMouseDown += MyTextBoxMouseEnter;
}
}
private static void MyTextBoxMouseEnter(object sender, MouseEventArgs e)
{
((TextBox)sender).SelectAll();
e.Handled = false;
}
public static readonly DependencyProperty SelectAllOnFocus
= DependencyProperty.RegisterAttached("SelectAllOnFocus", typeof(bool), typeof(TextBoxEscapeProperty),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnSelectAllOnFocus)));
}
What happens is the following:
The PreviewMouseDown event gets triggered.
The MyTextBoxMouseEnter method gets called.
The SelectAll() Method gets called.
When I do a "watch" on ((TextBox)sender).SelectedText, the value is correct (meaning whatever is in the textbox is showing up as selectedText).
The textbox itself is unchanged. No text is selected.
This is part of a general WPF style. All textboxes in the application should receive this property and it's associated behavior.
I'm stumped. Any ideas?
Thanks
What happens if you call ((TextBox)sender).UpdateLayout(); immediately after the SelectAll command? Or maybe you need to set the Keyboard focus to the text box.
It might be a better option to use something like this, which works if the text box is being selected with the mouse or the keyboard. (You'll need to modify it to check your "SelectAllOnFocus" property)
In your App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
// Select the text in a TextBox when it receives focus.
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyIgnoreMouseButton));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.MouseDoubleClickEvent, new RoutedEventHandler(SelectAllText));
base.OnStartup(e);
}
void SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e)
{
// Find the TextBox
DependencyObject parent = e.OriginalSource as UIElement;
while (parent != null && !(parent is TextBox))
parent = VisualTreeHelper.GetParent(parent);
if (parent != null)
{
var textBox = (TextBox)parent;
if (!textBox.IsKeyboardFocusWithin)
{
// If the text box is not yet focused, give it the focus and
// stop further processing of this click event.
textBox.Focus();
e.Handled = true;
}
}
}
void SelectAllText(object sender, RoutedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null)
textBox.SelectAll();
}

WPF - reset ListBox scroll position when ItemsSource changes

I currently have a ListBox whose ItemsSource collection is bound to a property on my viewmodel, of type IEnumerable. When that preoprty's reference changes, the ListBox updates as expected, however I have a problem in that if I have a large collection of items and scroll to the bottom of the ListBox, and then change the reference to another collection containing, say, 1 item, the ListBox view is blank and no scrollbar is displayed. I have to then scroll the listbox up with the mouse wheel, until the 1 item comes into view.
So, what I think I'm after, is a way of resetting the scroll position of the ListBox to the top, whenever the ItemsSource property changes, so that something is always displayed no matter how large or small the collection.
I'm unable to reproduce your problem (for me, the ListBox is scrolled to the last item in the new collection when changing ItemsSource). Anyway, to scroll the ListBox to the top every time its ItemsSource changes you can use some code behind. First listen to changes in the ItemsSourceProperty and then scroll the ListBox to the top once its items has been generated
Update
Made an attached behavior that does this instead to avoid code behind. It can be used like this
<ListBox ...
behaviors:ScrollToTopBehavior.ScrollToTop="True"/>
ScrollToTopBehavior
public static class ScrollToTopBehavior
{
public static readonly DependencyProperty ScrollToTopProperty =
DependencyProperty.RegisterAttached
(
"ScrollToTop",
typeof(bool),
typeof(ScrollToTopBehavior),
new UIPropertyMetadata(false, OnScrollToTopPropertyChanged)
);
public static bool GetScrollToTop(DependencyObject obj)
{
return (bool)obj.GetValue(ScrollToTopProperty);
}
public static void SetScrollToTop(DependencyObject obj, bool value)
{
obj.SetValue(ScrollToTopProperty, value);
}
private static void OnScrollToTopPropertyChanged(DependencyObject dpo,
DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = dpo as ItemsControl;
if (itemsControl != null)
{
DependencyPropertyDescriptor dependencyPropertyDescriptor =
DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
if (dependencyPropertyDescriptor != null)
{
if ((bool)e.NewValue == true)
{
dependencyPropertyDescriptor.AddValueChanged(itemsControl, ItemsSourceChanged);
}
else
{
dependencyPropertyDescriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
}
}
}
}
static void ItemsSourceChanged(object sender, EventArgs e)
{
ItemsControl itemsControl = sender as ItemsControl;
EventHandler eventHandler = null;
eventHandler = new EventHandler(delegate
{
if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(itemsControl) as ScrollViewer;
scrollViewer.ScrollToTop();
itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler;
}
});
itemsControl.ItemContainerGenerator.StatusChanged += eventHandler;
}
}
And an implementation of GetVisualChild
private 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;
}
Late answer:
A simple solution is to add an event handler for the TargetUpdated event, and set NotifyOnTargetUpdated=True on the ItemsSource binding:
<ListBox x:Name="listBox"
ItemsSource="{Binding MySource, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
and in the event handler, scroll to the top item:
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (listBox.Items.Count > 0)
{
listBox.ScrollIntoView(listBox.Items[0]);
}
}
Try this:
if (listBox.Items.Count > 0) {
listBox.ScrollIntoView(listBox.Items[0]);
}
Improved Fredrik Hedblad's answer to work with ObservableCollection:
public static class ItemsControlAttachedProperties
{
#region ScrollToTopOnItemsSourceChange Property
public static readonly DependencyProperty ScrollToTopOnItemsSourceChangeProperty =
DependencyProperty.RegisterAttached(
"ScrollToTopOnItemsSourceChange",
typeof(bool),
typeof(ItemsControlAttachedProperties),
new UIPropertyMetadata(false, OnScrollToTopOnItemsSourceChangePropertyChanged));
public static bool GetScrollToTopOnItemsSourceChange(DependencyObject obj)
{
return (bool) obj.GetValue(ScrollToTopOnItemsSourceChangeProperty);
}
public static void SetScrollToTopOnItemsSourceChange(DependencyObject obj, bool value)
{
obj.SetValue(ScrollToTopOnItemsSourceChangeProperty, value);
}
static void OnScrollToTopOnItemsSourceChangePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var itemsControl = obj as ItemsControl;
if (itemsControl == null)
{
throw new Exception("ScrollToTopOnItemsSourceChange Property must be attached to an ItemsControl based control.");
}
DependencyPropertyDescriptor descriptor =
DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
if (descriptor != null)
{
if ((bool) e.NewValue)
{
descriptor.AddValueChanged(itemsControl, ItemsSourceChanged);
}
else
{
descriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
}
}
}
static void ItemsSourceChanged(object sender, EventArgs e)
{
var itemsControl = sender as ItemsControl;
DoScrollToTop(itemsControl);
var collection = itemsControl.ItemsSource as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += (o, args) => DoScrollToTop(itemsControl);
}
}
static void DoScrollToTop(ItemsControl itemsControl)
{
EventHandler eventHandler = null;
eventHandler =
delegate
{
if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
var scrollViewer = GetVisualChild<ScrollViewer>(itemsControl);
scrollViewer.ScrollToTop();
itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler;
}
};
itemsControl.ItemContainerGenerator.StatusChanged += eventHandler;
}
static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < numVisuals; i++)
{
var v = (Visual) VisualTreeHelper.GetChild(parent, i);
child = v as T ?? GetVisualChild<T>(v);
if (child != null)
{
break;
}
}
return child;
}
#endregion
}
When you format the control, you select a range of cells as the selection choices which are then listed in the list box. You also select a cell as the link to the selected choices in which a number will be displayed depending on the position of the selection in the list. 1 for first in the list, 2 for second etc. The code is quite simply:-
Range("A1")Select
Selection = 1
Change ("A1") to the cell you have linked
and change the 1 to the position in the list you want selected.
The cell reference being a link works both ways - if you change your selection, the number in the cell changes and if you change the number in the cell, the highlighted selection changes.

Resources