Itemsstackpanel disables scrolling - c#

I have an Itemsstackpanel inside a Listview Control. I want to fire the PointerWheelChanged event whenever the User is near the edge of a page.
When i put the event on the itemsstackpanel is disables my ability to scroll by mouse wheel. If the event is on the Listview or Grid itself it only works as long as no Items are done loading in the Listview.
Is this intended behaviour or am i missing some important information?
I researched but found no lead to this problem or behaviour.
below is my XAML:
<Grid Background="Gray">
<ProgressRing x:Name="progress" IsActive="False" x:FieldModifier="public" Foreground="Black" Height="200" Width="200"/>
<ListView x:Name="ListViewControl" x:FieldModifier="public" Margin="10,10,10,10" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.ZoomMode="Enabled" ScrollViewer.VerticalScrollBarVisibility="Visible" DataFetchSize="10" IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="2" ShowsScrollingPlaceholders="True" BorderThickness="1" IsItemClickEnabled="False" SelectionMode="None" PointerEntered="ListViewControl_PointerEntered">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel CacheLength="0" Orientation="Vertical" Background="White" PointerWheelChanged="Grid_PointerWheelChanged"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
and the code behind(not done just trying to get it to work for now):
private void ItemsStackPanel_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
e.Handled = true;
PointerPoint pointerPoint = e.GetCurrentPoint(ListViewControl);
float scrolledDistance = pointerPoint.Properties.MouseWheelDelta;
if (scrolledDistance >=120)
{
//Load Page above current Page at a certain mousewheel point
}
else if (scrolledDistance <= -120 )
{
//Load Page below current Page at certain mousewheel point
}
else
{
//do some other stuff
}
}

The default ControlTemplate of ListView contains a ScrollViewer control. PointerWheelChanged is a relatively low-level event that will be intercepted by ScrollViewer.
If you want to monitor the change of the scrolling distance, PointerWheelChanged is not a recommended event. You can listen to the ScrollViewer.ViewChanged event and use ScrollViewer.VerticalOffset to determine the vertical scrolling distance.
We can create a custom ListView to achieve this:
CustomListView.cs
public class CustomListView:ListView
{
private ScrollViewer _scrollViewer;
public event EventHandler<ScrollViewerViewChangedEventArgs> ViewChanged;
public CustomListView()
{
this.DefaultStyleKey = typeof(ListView);
}
protected override void OnApplyTemplate()
{
_scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;
if (_scrollViewer != null)
{
_scrollViewer.ViewChanged += CustomViewChange;
}
base.OnApplyTemplate();
}
private void CustomViewChange(object sender, ScrollViewerViewChangedEventArgs e)
{
ViewChanged?.Invoke(sender, e);
}
}
Usage
<controls:CustomListView x:Name="ListViewControl"
ViewChanged="ListViewControl_ViewChanged"
...>
<!--other content-->
</controls:CustomListView>
private void ListViewControl_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
double scrollHeight = scrollViewer.VerticalOffset;
if (scrollHeight > 120)
{
//Do Something...
}
else
{
//Do something...
}
}

Related

Easy Drag Drop implementation MVVM

I am new to MVVM and I am currently trying to add the drag/drop feature to my application. The thing is I already developed the interface in the code-behind but I am trying now to re-write the code into MVVM as I am only at the beginning of the project.
Here is the context: the user will be able to add boxes (ToggleButton but it may change) to a grid, a bit like a chessboard. Below is the View Model I am working on:
<Page.Resources>
<Style TargetType="{x:Type local:AirportEditionPage}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Page}">
<!-- The page content-->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ToolKitWidth, FallbackValue=50}" />
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding RightPanelWidth, FallbackValue=400}"/>
</Grid.ColumnDefinitions>
<!-- The airport grid where Steps and Links are displayed -->
<ScrollViewer Grid.ColumnSpan="4" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Viewbox Height="{Binding AirportGridHeight}" Width="{Binding AirportGridWidth}" RenderOptions.BitmapScalingMode="HighQuality">
<ItemsControl x:Name="ChessBoard" ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="{Binding CardQuantityRow}" Height="{Binding CardQuantityColumn}" Background="{StaticResource AirportGridBackground}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="1" Height="1">
<ToggleButton Style="{StaticResource StepCardContentStyle}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Pos.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Pos.Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Viewbox>
</ScrollViewer>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
Items are basically from a class (child of INotifiedPropertyChanged) with a name, an icon and a position (Point).
Now, I am trying to make the user able to drag and drop the box (ToggleButton) within the grid wherever he/she wants. However, I am totally lost with Commands, AttachedProperties etc. I spent all the whole day on tutorials and tried drag/drop solutions but with my poor knowledge, I don't know how to apply all of this into my code.
On my code-behinded version of the code, it was easy. When the button is left-clicked, I say to a variable of the grid "hey, I am being dragged and dropped". While the user is moving, I changed the Item coordinates and when the user released the left button (left button up), the dragdrop_object variable comes null again.
In the frame of the MVVM, I am totally lost. Could you give me some tracks to help me trough ? I intended to give up with MVVM a lot of time, but I know that it is better to keep up even if every little feature takes litteraly hours for me to implement (it should decrease with time...).
Do not hesitate if you need further details to answer to my question.
I found the solution here : Move items in a canvas using MVVM and here : Combining ItemsControl with draggable items - Element.parent always null
To be precise, here is the code I added :
public class DragBehavior
{
public readonly TranslateTransform Transform = new TranslateTransform();
private static DragBehavior _instance = new DragBehavior();
public static DragBehavior Instance
{
get { return _instance; }
set { _instance = value; }
}
public static bool GetDrag(DependencyObject obj)
{
return (bool)obj.GetValue(IsDragProperty);
}
public static void SetDrag(DependencyObject obj, bool value)
{
obj.SetValue(IsDragProperty, value);
}
public static readonly DependencyProperty IsDragProperty =
DependencyProperty.RegisterAttached("Drag",
typeof(bool), typeof(DragBehavior),
new PropertyMetadata(false, OnDragChanged));
private static void OnDragChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// ignoring error checking
var element = (UIElement)sender;
var isDrag = (bool)(e.NewValue);
Instance = new DragBehavior();
((UIElement)sender).RenderTransform = Instance.Transform;
if (isDrag)
{
element.MouseLeftButtonDown += Instance.ElementOnMouseLeftButtonDown;
element.MouseLeftButtonUp += Instance.ElementOnMouseLeftButtonUp;
element.MouseMove += Instance.ElementOnMouseMove;
}
else
{
element.MouseLeftButtonDown -= Instance.ElementOnMouseLeftButtonDown;
element.MouseLeftButtonUp -= Instance.ElementOnMouseLeftButtonUp;
element.MouseMove -= Instance.ElementOnMouseMove;
}
}
private void ElementOnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
((UIElement)sender).CaptureMouse();
}
private void ElementOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
((UIElement)sender).ReleaseMouseCapture();
}
private void ElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
FrameworkElement element = sender as FrameworkElement;
Canvas parent = element.FindAncestor<Canvas>();
var mousePos = mouseEventArgs.GetPosition(parent);
if (!((UIElement)sender).IsMouseCaptured) return;
if (mousePos.X < parent.Width && mousePos.Y < parent.Height && mousePos.X >= 0 && mousePos.Y >=0)
((sender as FrameworkElement).DataContext as Step).Pos = new System.Drawing.Point(Convert.ToInt32(Math.Floor(mousePos.X)), Convert.ToInt32((Math.Floor(mousePos.Y))));
}
}
And my DataTemplate is now:
<DataTemplate>
<ContentControl Height="1" Width="1" local:DragBehavior.Drag="True" Style="{StaticResource StepCardContentControl}"/>
</DataTemplate>
I added the FindAncestor static class in a dedicated file like this:
public static class FindAncestorHelper
{
public static T FindAncestor<T>(this DependencyObject obj)
where T : DependencyObject
{
DependencyObject tmp = VisualTreeHelper.GetParent(obj);
while (tmp != null && !(tmp is T))
{
tmp = VisualTreeHelper.GetParent(tmp);
}
return tmp as T;
}
}
(My items are now ContentControls).
As the items' positions within the canvas are directly managed with their Pos variable (Canvas.SetLeft and Canvas.SetTop based on Pos (Pos.X and Pos.Y) with Binding), I just update it according to the MousePosition within the Canvas.
Also, as suggested in a commentary, I will see if there is something better than the ScrollViewer and Viewbox I'm using.

How can I prevent touch-click when touch-scrolling?

I have an ItemsControl that wraps its ItemPresenter with a ScrollViewer. That ItemPresenter displays a ListView. Therefore I have a collection within a collection.
Now, I want only the ScrollViewer to have scrolling functionality so I have gone ahead and removed the scrolling functionality from the inner ListView.
The problem is that my scrolling event is being messed up by the ListView. As soon as my finger touches the content area it selects the ListViewItems instead of scrolling.
How can I tell through routed events if the user is trying to click or scroll? and if it is scroll, how do I prevent it from selecting the ListViewItems?
<ItemsControl ItemsSource="{Binding Countries}" >
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer PanningMode="VerticalOnly">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Cities}">
<ListView.Template>
<ControlTemplate>
<ItemsPresenter/>
</ControlTemplate>
</ListView.Template>
</ListView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There isn't any way to look into the future to see if a user is going to begin scrolling after touching the screen or simply lift their finger off immediately afterwards. A Future API would be nice, though.
Anyhow, you could just check to see if the user has moved their finger at all after touching the ListView. If so, begin treating the "touch" like a "scroll" rather than a "click" by manually scrolling the ScrollViewer and deselecting the ListView items.
Something like this:
private bool _touchDown = false;
private double _initOffset = 0;
private double _scrollDelta = 5;
private void ListView_PreviewTouchDown(object sender, TouchEventArgs e)
{
_touchDown = true;
_initOffset = e.GetTouchPoint(this).Y;
}
private void ListView_PreviewTouchMove(object sender, TouchEventArgs e)
{
if (_touchDown && Math.Abs(r.GetTouchpoint(this).Y - _initOffset) > _scrollDelta)
{
My_ScrollViewer.ScrollToVerticalOffset(r.GetTouchpoint(this).Y - _initOffset);
My_ListView.UnselectAll();
}
}
private void ListView_PreviewTouchUp(object sender, TouchEventArgs e)
{
_touchDown = false;
_initOffset = 0;
}
Disclaimer: I just wrote this in notepad. It has problems, but it gets the concept across.

Cross Sliding doesn't work in UWP

I am working to enable Horizontal swipe / flick horizontally in my UWP app. I have followed MSDN link to add GestureRecognizer. It works well, If I have only Grid and TextBlock control in my XAML Page. However, I have Grid and ListBox in my XAML Page. It doesn't work with ListBox
Here is a sample;
class GestureInputProcessor
{
Windows.UI.Input.GestureRecognizer gestureRecognizer;
UIElement element;
public GestureInputProcessor(Windows.UI.Input.GestureRecognizer gr, Windows.UI.Xaml.UIElement target)
{
gestureRecognizer = gr;
element = target;
gestureRecognizer.GestureSettings =
Windows.UI.Input.GestureSettings.Tap |
Windows.UI.Input.GestureSettings.Hold | Windows.UI.Input.GestureSettings.RightTap |
Windows.UI.Input.GestureSettings.CrossSlide;
// Set up pointer event handlers. These receive input events that are used by the gesture recognizer.
element.PointerCanceled += OnPointerCanceled;
element.PointerPressed += OnPointerPressed;
element.PointerReleased += OnPointerReleased;
element.PointerMoved += OnPointerMoved;
// Set up event handlers to respond to gesture recognizer output
Windows.UI.Input.CrossSlideThresholds threshold = new Windows.UI.Input.CrossSlideThresholds();
threshold.SelectionStart = 2;
threshold.SpeedBumpStart = 3;
threshold.SpeedBumpEnd = 4;
threshold.RearrangeStart = 5;
gestureRecognizer.CrossSlideHorizontally = true;
gestureRecognizer.CrossSlideThresholds = threshold;
gestureRecognizer.CrossSliding += GestureRecognizer_CrossSliding;
}
private void GestureRecognizer_CrossSliding(Windows.UI.Input.GestureRecognizer sender, Windows.UI.Input.CrossSlidingEventArgs args)
{
MessageService.showMessage("You are here", MessageType.Information);
}
void OnPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs args)
{
// Route teh events to the gesture recognizer
gestureRecognizer.ProcessDownEvent(args.GetCurrentPoint(element));
// Set the pointer capture to the element being interacted with
element.CapturePointer(args.Pointer);
// Mark the event handled to prevent execution of default handlers
args.Handled = true;
}
void OnPointerCanceled(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs args)
{
gestureRecognizer.CompleteGesture();
args.Handled = true;
}
void OnPointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs args)
{
gestureRecognizer.ProcessUpEvent(args.GetCurrentPoint(element));
args.Handled = true;
}
void OnPointerMoved(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs args)
{
gestureRecognizer.ProcessMoveEvents(args.GetIntermediatePoints(element));
}
}
XAML Page;
<Grid x:Name="LayoutRoot" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<ListBox x:Name="lsbReadingChapter" ItemsSource="{Binding ChapterContent}" SelectedItem="{Binding SelectedAya}" DoubleTapped="lsbReadingChapter_DoubleTapped"
FlowDirection="RightToLeft" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Visibility="{Binding Visiblity}" HorizontalAlignment="Stretch" FlowDirection="{Binding TranslationLanguage.FlowDirection}" FontSize="{Binding TranslationFont.FontSize}" TextWrapping="Wrap" FontFamily="{Binding TranslationFont.FontPath}" Text="{Binding AyaTranslation}">
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
XAML Page Contstructor
//Handle horizental / vertical swipe event
GestureRecognizer gr1 = GestureRecognizer();
public MainPage()
{
//swipe event this works well, If I have TextBlock instead of ListBox
GestureInputProcessor ShapeInput1 = new GestureInputProcessor(gr1, LayoutRoot);
}
The pointer events are also fired in ListBox but the gesture doesn't work. I am looking to enable swipe left / right in my Listbox control. is it possible?
Thanks!

Give focus to a component inside an expander

I have this requirement where I need to focus the first element inside the expander when the user press tab.
Currently (default behavior) the focus goes to the expander, I've tried to focus the first element of the expander by creating a focus event handler in the expander like this:
private void ExpanderGotFocus(object sender, RoutedEventArgs e)
{
var expander = (Expander) sender;
if (!expander.IsExpanded)
{
expander.IsExpanded = true;
this._someText.Focus();
}
}
Which doesn't work.
I've also tried to give the focus the the next element:
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
var keyboardFocus = Keyboard.FocusedElement as UIElement;
keyboardFocus.MoveFocus(tRequest);
But only works the second time ( when the expander has been at least opened once )
I've tried to put this in a thread and some other crazy ideas.
How can I give focus to the first element inside an expander? ( the first time the expander is closed )
I tried several ways and none of them worked, basically the problem is the TextBox is still rendering when the expander is expanding ( to early ).
So instead what I've found is to add the IsVisibleChanged event to the textbox so when the expander finished the textbox become visible and request the focus
XAML
<Expander GotFocus="ExpanderGotFocus">
<Expander.Header>
<TextBlock Text="{x:Static Client:Strings.XYZ}" />
</Expander.Header>
<Expander.Content>
<StackPanel>
<TextBox IsVisibleChanged="ControlIsVisibleChanged" Name="txtBox" />
</StackPanel>
</Expander.Content>
</Expander>
Code behind
private void ExpanderGotFocus(object sender, RoutedEventArgs e)
{
var expander = (Expander) sender;
if (!expander.IsExpanded )
{
expander.IsExpanded = true;
}
}
private void ControlIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Keyboard.Focus((IInputElement)sender);
}
Check with the following,
XAML code:
<StackPanel>
<Expander Header="Expander"
Name="expander"
Collapsed="OnCollapsed"
IsExpanded="True" >
<StackPanel>
<TextBox Text="Text1" Name="textBox1" />
<TextBox Text="Text2" Name="textBox2" />
<TextBox Text="Text3" Name="textBox3" />
</StackPanel>
</Expander>
<TextBox Text="Text4" Name="textBox4" />
</StackPanel>
in the code behind:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.Loaded += delegate
{
textBox2.Focus();
};
}
private void OnCollapsed(object sender, RoutedEventArgs e)
{
var element = Keyboard.FocusedElement;
if (element != null)
{
//now is the ToggleButton inside the Expander get keyboard focus
MessageBox.Show(element.GetType().ToString());
}
//move focus
Keyboard.Focus(textBox4);
}
}

Listview inside of scrollviewer prevents scrollviewer scroll

I have a scrollviewer with a couple listboxes in it. The problem is if a user uses the middle mouse roller to scroll the scrollviewer while their mouse is over a listview. The listview scrolls its internal scrollviewer to the bottom and then continues to capture the mouse, preventing the containing scrollviewer from scrolling.
Any ideas on how to handle this?
That happens because the ListView's (ListBox's, actually) content template wraps its items with a ScrollViewer by itself.
The simplest way is to disable it by dropping your own Template for the inside ListView, one that doesn't create a ScrollViewer:
<ListView>
<ListView.Template>
<ControlTemplate>
<ItemsPresenter></ItemsPresenter>
</ControlTemplate>
</ListView.Template>
...
</ListView>
BTW the same happens if you have a ListView inside a ListView (this was my case).
IMO, the best way to handle this scenario is to create a custom control :
class MyScrollViewer : ScrollViewer
{
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
{
base.OnPreviewMouseWheel(e);
if (!e.Handled)
{
e.Handled = true;
this.RaiseEvent(new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = this
});
}
}
}
Did you try disabling the ListView's ScrollBars?
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled" />
Inspired by some helpful answers, I have an implementation that scrolls ancestor ScrollViewers when inner ones (including from ListView, ListBox, DataGrid) scroll past their top/bottom.
I apply an attached property to all ScrollViewers in App.xaml:
<Style TargetType="ScrollViewer" BasedOn="{StaticResource {x:Type ScrollViewer}}">
<Setter Property="local:ScrollViewerHelper.FixMouseWheel" Value="True" />
</Style>
The attached property detects scrolling past top/bottom, and when that happens raises a mouse wheel event on the ScrollViewer's parent. Event routing gets it to the outer ScrollViewer:
public static class ScrollViewerHelper
{
// Attached property boilerplate
public static bool GetFixMouseWheel(ScrollViewer scrollViewer) => (bool)scrollViewer?.GetValue(FixMouseWheelProperty);
public static void SetFixMouseWheel(ScrollViewer scrollViewer, bool value) => scrollViewer?.SetValue(FixMouseWheelProperty, value);
public static readonly DependencyProperty FixMouseWheelProperty =
DependencyProperty.RegisterAttached("FixMouseWheel", typeof(bool), typeof(ScrollViewerHelper),
new PropertyMetadata(OnFixMouseWheelChanged));
// End attached property boilerplate
static void OnFixMouseWheelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var scrollViewer = d as ScrollViewer;
if (scrollViewer == null) return;
scrollViewer.PreviewMouseWheel += (s2, e2) =>
{
var parent = scrollViewer.Parent as UIElement;
bool hitTopOrBottom = HitTopOrBottom(e2.Delta, scrollViewer);
if (parent is null || !hitTopOrBottom) return;
var argsCopy = Copy(e2);
parent.RaiseEvent(argsCopy);
};
}
static bool HitTopOrBottom(double delta, ScrollViewer scrollViewer)
{
var contentVerticalOffset = scrollViewer.ContentVerticalOffset;
var atTop = contentVerticalOffset == 0;
var movedUp = delta > 0;
var hitTop = atTop && movedUp;
var atBottom =
contentVerticalOffset == scrollViewer.ScrollableHeight;
var movedDown = delta < 0;
var hitBottom = atBottom && movedDown;
return hitTop || hitBottom;
}
static MouseWheelEventArgs Copy(MouseWheelEventArgs e)
=> new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = e.Source,
};
}
If you wrap the inner listview in a scrollviewer then the scrolling will work.
<ListView ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Categories