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);
}
}
});
}
Related
I have an WPF app using MVVM that shows a log in a ListView control. I have it bound to an ObservableCollection and the control updates when items are added.
I have it coded so that when it starts, it automatically scrolls to the top when an item is inserted into the collection at position 0 so it always shows the latest log message. This works on all machines I have tested.
When a user does something on the ListView (clicks or scrolls), the automatic scrolling is turned off so the user can look at any part of the log they want. When they are finished looking at the log, they can click a button to turn the automatic scrolling back on. Everything works except on one of my test machines, the view changes as items are added. On my dev machine and another test machine, the ListView window does not change when things are added to the collection. Same code/config files are used for all systems.
For example:
The user scrolls to show "My Item" at the top of the ListView.
Another log message is added.
I want "My Item" to still show at the top of the ListView.
Dev machine and one Test machine: "My Item" shows at the top of the ListView.
Another Test Machine: "My Item" is now in the second row of the ListView.
The original change request was because this scrolling was not working on some machines but not others. After much time, there seems to be a difference in the machines themselves that is effecting this.
Is there some system setting that would control this?
XAML:
<ListView Grid.Row="1" Grid.Column="9" Name="messagesListView"
Grid.ColumnSpan="3" Margin="8,0,40,0"
ItemsSource="{Binding StatusMessagesList}"
SelectionChanged="messagesListView_SelectionChanged"
PreviewMouseWheel="messagesListView_PreviewMouseWheel"
MouseDoubleClick="batchesListView_MouseDoubleClick"
PreviewMouseDown="messagesListView_MouseDown"
HorizontalAlignment="Stretch">
</ListView>
Code Behind:
private void ScrollToTop()
{
scrollPos = 0;
ScrollSpot.Text = scrollPos.ToString();
ScrollToPos();
}
private void ScrollToPos()
{
ScrollViewer scrollViewer = GetScrollViewer(messagesListView) as ScrollViewer;
if (scrollViewer != null && _viewState)
scrollViewer.ScrollToVerticalOffset(scrollPos);
}
private void autoScrollButton_Click(object sender, RoutedEventArgs e)
{
messagesListView.SelectedItem = null;
autoScrollButton.Visibility = Visibility.Hidden;
_viewState = true;
ScrollToTop();
}
private void ListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
ScrollViewer scrollViewer = GetScrollViewer(messagesListView) as ScrollViewer;
if (scrollViewer != null && _viewState)
{
ScrollToTop();
}
if(scrollViewer != null && !_viewState)
{
scrollPos += e.NewItems.Count;
ScrollSpot.Text = scrollPos.ToString();
ScrollToPos();
}
}
}
private void messagesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
private void messagesListView_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
private void messagesListView_Scroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
private void messagesListView_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource.GetType().ToString().IndexOf("Rectangle") >= 0)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
}
}
Code to Add to Collection:
{
if (_statusMessagesList == null)
StatusMessagesList = new ObservableCollection<string>();
string stMsg = string.Format("{0} {1} {2}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), message);
StatusMessagesList.Insert(0, stMsg);
Thanks,
Brad P.
Update: I never found out why one system worked and the other did not. However, the solution I found that worked on both was to set messagesListView.CanContentScroll = False
and then manually keep track of the Offset Position on the ScrollViewer:
{
SetUpManualScroll(0);
}
private void messagesListView_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
double scrollAmt = (e.Delta / 120) * -48;
SetUpManualScroll(scrollAmt);
}
private void messagesListView_Scroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)
{
SetUpManualScroll(e.NewValue);
}
private void messagesListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
SetUpManualScroll(0);
}
private void messagesListView_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource.GetType().ToString().IndexOf("TextBlock") < 0 && e.OriginalSource.GetType().ToString().IndexOf("Border") < 0)
{
if (_scrollviewer.VerticalOffset != 0)
SetUpManualScroll(0);
else
{
messagesListView.SelectedItem = null;
autoScrollButton.Visibility = Visibility.Hidden;
_viewState = true;
ScrollToTop();
}
}
}
private void SetUpManualScroll(double d)
{
_viewState = false;
autoScrollButton.Visibility = Visibility.Visible;
_scrollviewer.UpdateLayout();
double newPos = _scrollviewer.VerticalOffset + d;
if (newPos < 0)
newPos = 0;
ScrollPos = newPos;
}
private void ScrollToTop()
{
ScrollPos = 0;
ScrollToPos();
}
private void ScrollToPos()
{
if (_scrollviewer == null)
{
_scrollviewer = GetScrollViewer(messagesListView);
}
_scrollviewer.UpdateLayout();
_scrollviewer.ScrollToVerticalOffset(ScrollPos);
}
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);
}
}
}
}
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;
}
}
I'm trying to prevent the WPF Bing Maps control from panning when the user is dragging a pushpin. What I do is that when the user selecting the pushpin with the MouseLeftButtonDown I'm, taking over the events from the map ViewChangeStart, ViewChangeOnFrame and set the e.Handled property to true.
What I was expecting is that if I set the property to true the events are canceled and panning is disabled. However the map is still panning.
Another approach what I tried is setting the property SupportedManipulations to None. Both options don't have the expected results.
Below is the code that I'm using for my DraggablePushpin
public class DraggablePushpin : Pushpin
{
private bool isDragging = false;
protected override void OnPreviewMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
{
var parentLayer = this.Parent as MapLayer;
if (parentLayer != null)
{
Map parentMap = parentLayer.Tag as Map;
if (parentMap != null)
{
parentMap.ViewChangeStart += parentMap_ViewChangeStart;
parentMap.MouseLeftButtonUp += parentMap_MouseLeftButtonUp;
parentMap.MouseMove += parentMap_MouseMove;
parentMap.SupportedManipulations = System.Windows.Input.Manipulations.Manipulations2D.None;
}
}
this.isDragging = true;
base.OnPreviewMouseLeftButtonDown(e);
}
protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
}
void parentMap_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
var map = sender as Map;
// Check if the user is currently dragging the Pushpin
if (this.isDragging)
{
// If so, the Move the Pushpin to where the Mouse is.
var mouseMapPosition = e.GetPosition(map);
var mouseGeocode = map.ViewportPointToLocation(mouseMapPosition);
this.Location = mouseGeocode;
}
}
void parentMap_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
(sender as Map).SupportedManipulations = System.Windows.Input.Manipulations.Manipulations2D.All;
}
protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)
{
var parentLayer = this.Parent as MapLayer;
if (parentLayer != null)
{
Map parentMap = parentLayer.Tag as Map;
if (parentMap != null)
{
parentMap.SupportedManipulations = System.Windows.Input.Manipulations.Manipulations2D.All;
}
}
}
void parentMap_ViewChangeStart(object sender, MapEventArgs e)
{
if (this.isDragging)
{
e.Handled = true;
}
}
}
I want to create Adorner for FrameworkElement in attached property. But in PropertyChangedCallback AdornerLayer for my element is null.
How can I solve this issue?
Currently I do this:
private static void _OnIsModalAdornerAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (null != element && oldValue != newValue)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(VisibilityProperty, typeof(FrameworkElement));
if (newValue)
{
descriptor.AddValueChanged(element, element_VisibilityChanged);
}
else
{
descriptor.RemoveValueChanged(element, element_VisibilityChanged);
}
}
private static void element_VisibilityChanged(object sender, EventArgs e)
{
var element = sender as FrameworkElement;
if (null != element)
{
var adornerLayer = AdornerLayer.GetAdornerLayer(element);
if (null != adornerLayer)
{
// check if adorner exists
bool isExists = false;
var adorners = adornerLayer.GetAdorners(element);
if (null != adorners)
{
foreach (var adorner in adorners)
{
if (adorner is ModalAdorner)
{
isExists = true;
break;
}
}
}
// add if is not presented
if (!isExists)
{
var modalAdorner = new ModalAdorner(element);
adornerLayer.Add(modalAdorner);
var visibilityBinding = new Binding { Path = new PropertyPath("Visibility"), Source = element };
modalAdorner.SetBinding(VisibilityProperty, visibilityBinding);
}
}
}
}
Then I manually change Visibility property for my FrameworkElementto Hidden and then to Visible. But this way is not really true.
UPDATE
I solved this issue. Now I do so:
private static void _OnIsModalAdornerAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (null != element && oldValue != newValue)
{
if (newValue)
{
if (element.IsLoaded)
{
_AttachAdorner(element);
}
else
{
element.Loaded += (sender, args) =>
{
_AttachAdorner(element);
};
}
}
else
{
// remove adorner
var adornerLayer = AdornerLayer.GetAdornerLayer(element);
if (null != adornerLayer)
{
var adorners = adornerLayer.GetAdorners(element);
if (null != adorners)
{
foreach (var adorner in adorners)
{
if (adorner is ModalAdorner)
{
adornerLayer.Remove(adorner);
}
}
}
}
}
}
}
The answer is in update: use Loaded event.