Cross Sliding doesn't work in UWP - c#

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!

Related

Itemsstackpanel disables scrolling

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...
}
}

Drag and Drop in UWP in list of bank accounts

I have a Universal Windows Application for a local bank, I'm working on a money transfer view, and they need to transfer money from account to account using the Drag and Drop feature in UWP applications.
I've made the animation part, but I need help after I drop the list item to the "Account To" list.
I'll attach a screenshot to make it clear.
As you see in the picture, I need to drag one item from the "From Account" list and drop it on only one item on "To Account" list. How can I achieve this ?
I've created a small sample which shows drag-drop between two ListViews filled with some Accounts. I will skip the implementation of UserControls - the Page xaml looks like this:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<ListView Header="Source" Margin="10" Grid.Row="0" CanDragItems="True" ItemsSource="{x:Bind Accounts}" SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<controls:AccountControl CanDrag="True" DragStarting="AccountControl_DragStarting"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Header="Targets" Margin="10" Grid.Row="1" ItemsSource="{x:Bind Accounts}" SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<controls:AccountControl AllowDrop="True" DragEnter="AccountControl_DragEnter" Drop="AccountControl_Drop"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
As you can see there is a Source list in which the control is firing an event when it's being dragged.
private void AccountControl_DragStarting(UIElement sender, DragStartingEventArgs args)
{
if ((sender as AccountControl)?.DataContext is Account account)
{
args.AllowedOperations = DataPackageOperation.Link;
args.Data.SetData(accountId, account.Id);
}
}
The Account class apart from name and balance has a Guid identifier so I can use it to pass information which source account has been used in transfer method.
The items in second list (Targets) accepts only drop operation and for this purpose fire two events:
private void AccountControl_DragEnter(object sender, DragEventArgs e)
{
e.AcceptedOperation = DataPackageOperation.Link;
e.DragUIOverride.Caption = "Transfer";
}
private async void AccountControl_Drop(object sender, DragEventArgs e)
{
if ((e.OriginalSource as AccountControl)?.DataContext is Account targetAccount)
if (await (e.DataView.GetDataAsync(accountId)) is Guid sourceAccountId)
{
var sourceAccount = Accounts.First(x => x.Id == sourceAccountId);
sourceAccount.Balance -= 1000;
targetAccount.Balance += 1000;
}
}
The first one sets accepted operation and some information for the user. The second one 'transfers' some money from one account to the second.
Everything looks like this:
Some more help you can find at MS directly, other article and in MS samples repository.
I am not fully satisfied with the "solutions" which I will provide. They are much likely very far away from the ideal implementations, but ...
The XAML code which I created to try to replicate as easily, but also consistently your object, consisted in a group of draggable Rectangles inside a StackPanel Control, plus another StackPanel Control where the items could be dragged into.
<Grid>
<Grid.Resources>
<Style TargetType="Rectangle">
<Setter Property="Width" Value="300"/>
<Setter Property="Height" Value="300"/>
<Setter Property="CanDrag" Value="True"/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Name="StackPanelRectangles" Grid.Row="0" Orientation="Horizontal">
<Rectangle x:Name="RedRect" Fill="Red" DragStarting="Rectangle_DragStarting"/>
<Rectangle x:Name="GreenRect" Fill="Green" DragStarting="Rectangle_DragStarting"/>
<Rectangle x:Name="BlueRect" Fill="Blue" DragStarting="Rectangle_DragStarting"/>
</StackPanel>
<StackPanel Name="StackPanelDropArea" Background="Azure" AllowDrop="True"
DragOver="StackPanel_DragOver" Drop="StackPanel_Drop"
Grid.Row="2" Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock>Drop anywhere in this area area</TextBlock>
</StackPanel>
</Grid>
1st Solution:
I routed every DragStarting event of the multiple Rectangles to the same EventHandler. In this EventHandler, we have access to the UIElement which is being dragged, so with an exposed property of type UIElement in your Page class, and you can simply clone the necessary properties for when you need to drop it, like this:
UIElement dragItem;
private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
dataPackage.RequestedOperation = DataPackageOperation.Copy;
dragItem = sender;
}
Then when the item is dropped EventHandler is called, I have simply add it onto my DropArea.
private void StackPanel_Drop(object sender, DragEventArgs e)
{
Rectangle newElement = new Rectangle();
newElement.Width = (dragItem as Rectangle).Width;
newElement.Height = (dragItem as Rectangle).Height;
newElement.Fill = (dragItem as Rectangle).Fill;
StackPanelDropArea.Children.Add(newElement);
}
You cannot add your new Control by setting to reference the object being dragged, since there are properties such as the respective Parent which will thrown an exception when you try to add the Control to a different container.
2nd Solution:
I was extremely focused on on utilizing the DataPackage object, and one of its supported default formats, but I don't think any of them can actually hold data of an Object, such as our UIElement.
But each DataPackage instance supports a set of properties, which corresponds a Dictionary. We can set the Dictionary to hold UIElement in there, as long as we specify a key to reference that same object later on.
private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
dataPackage.RequestedOperation = DataPackageOperation.Copy;
args.Data.Properties.Add("myRectangle", sender);
}
In the drop Event Handler, you can obtain the UIElement, like such:
private async void StackPanel_Drop(object sender, DragEventArgs e)
{
Rectangle element = e.DataView.Properties["myRectangle"] as Rectangle;
......
......
}
3rd Solution:
This solution used the method SetText(String) exposed by DataPackage, to hold the value of the Name property of the UIElement being dragged.
private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
dataPackage = new DataPackage();
dataPackage.RequestedOperation = DataPackageOperation.Copy;
Rectangle rectangle = sender as Rectangle;
dataPackage.SetText(rectangle.Name);
Clipboard.SetContent(dataPackage);
}
By knowing the value of the Name property of the UIElement which is being dragged, looked for it, by using the VisualTreeHelper Class, like this:
private async void StackPanel_Drop(object sender, DragEventArgs e)
{
DataPackageView dataPackageView = Clipboard.GetContent();
if (dataPackageView.Contains(StandardDataFormats.Text))
{
draggedObject = await dataPackageView.GetTextAsync();
}
// Dragged objects come from another one of our Parent's Children
DependencyObject parent = VisualTreeHelper.GetParent(StackPanelDropArea);
int count = VisualTreeHelper.GetChildrenCount(parent);
for(int i=0; i< count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if(child.GetType().Equals(typeof(StackPanel)))
{
StackPanel currentStackPanel = child as StackPanel;
if(currentStackPanel.Name == "StackPanelRectangles")
{
int numberOfRectangles = VisualTreeHelper.GetChildrenCount(currentStackPanel);
for(int j=0; j<numberOfRectangles; j++)
{
if(VisualTreeHelper.GetChild(currentStackPanel,j).GetType().Equals(typeof(Rectangle)))
{
Rectangle currentRectangle = VisualTreeHelper.GetChild(currentStackPanel, j) as Rectangle;
if (draggedObject != string.Empty && currentRectangle.Name.Equals(draggedObject))
{
Rectangle newRectangle = new Rectangle();
newRectangle.Width = currentRectangle.Width;
newRectangle.Height = currentRectangle.Height;
newRectangle.Fill = currentRectangle.Fill;
StackPanelDropArea.Children.Add(newRectangle);
}
}
}
}
}
} */
}
Result:
I usually try tackling this several times before giving up and using a third party library. The one I typically use is:
https://github.com/punker76/gong-wpf-dragdrop
You may subscribe to PointerPressed event in your DataTemplate and extract all the things you need.
XAML:
<DataTemplate x:Name="DataTemplate">
<Grid Background="Transparent" PointerPressed="Grid_OnPointerPressed"/>
</DataTemplate>
Code:
private void Grid_OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
//your FrameworkElement
var frameworkElement = sender as FrameworkElement;
//global position of your element
var itemPosition = frameworkElement.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0)).ToVector2();
//your data
var selectedItemData = frameworkElement.DataContext as ItemData;
}
Save your data, use UWP Drag'n'Drop. On drop load your data.

What event fires when user lifts finger from ScrollViewer on touch capable screens

I'm finding that when I tap the ScrollViewer, the PointerPressed and PointerExited events fires as expected. But, if I scroll in any direction after touching the screen and lift my finger, no event fires except for PointerCaptureLost which prematurely fires as soon as I scroll.
When I capture the pointer ID and poll the status of the PointerPoint with a timer, the IsInContact flag remains true, even after I lift my finger after scrolling. It works as expected when I simply tap the screen.
ManipulationCompleted has the same effect as above, and I cannot use the ViewChanged event since this fires before I lift my finger.
Is this a bug or am I missing something here? Is there another way I can detect when a user has lifted their finger off the screen? This is driving me bananas.
Sample code below. You'll need to use the simulator in touch-mode or have a touch capable screen to test:
Code:
using System;
using Windows.UI.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
namespace App1
{
public sealed partial class MainPage : Page
{
private readonly DispatcherTimer pointerTimer = new DispatcherTimer();
private uint? CurrentPointerID; //container for the current pointer id when user makes contact with the screeen
public MainPage()
{
this.InitializeComponent();
scrollviewer.PointerPressed += scrollviewer_PointerPressed;
scrollviewer.PointerMoved += scrollviewer_PointerMoved;
scrollviewer.PointerExited += scrollviewer_PointerExited;
scrollviewer.PointerReleased += scrollviewer_PointerReleased;
scrollviewer.PointerCaptureLost += scrollviewer_PointerCaptureLost;
scrollviewer.PointerCanceled += scrollviewer_PointerCanceled;
pointerTimer.Tick += pointerTimer_Tick;
pointerTimer.Interval = TimeSpan.FromMilliseconds(300);
pointerTimer.Start();
}
#region ScrollViewer Events
void scrollviewer_PointerMoved(object sender, PointerRoutedEventArgs e)
{
EventCalledTextBlock.Text = "Pointer Moved";
}
void scrollviewer_PointerExited(object sender, PointerRoutedEventArgs e)
{
EventCalledTextBlock.Text = "Pointer Exited";
}
void scrollviewer_PointerPressed(object sender, PointerRoutedEventArgs e)
{
CurrentPointerID = e.Pointer.PointerId;
EventCalledTextBlock.Text = "Pointer Pressed";
}
void scrollviewer_PointerCanceled(object sender, PointerRoutedEventArgs e)
{
EventCalledTextBlock.Text = "Pointer Canceled";
}
void scrollviewer_PointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
EventCalledTextBlock.Text = "Capture Lost";
}
void scrollviewer_PointerReleased(object sender, PointerRoutedEventArgs e)
{
EventCalledTextBlock.Text = "Pointer Released";
}
#endregion
void pointerTimer_Tick(object sender, object e)
{
if (!CurrentPointerID.HasValue)
{
PollingTextBlock.Text = string.Empty;
return;
}
try
{
var pointerPoint = PointerPoint.GetCurrentPoint(CurrentPointerID.Value);
PollingTextBlock.Text = pointerPoint.IsInContact ? "Is In Contact" : "Not in Contact";
}
catch (Exception ex)
{
//This exception is raised when the user lifts finger without dragging.
//assume finger is not in contact with screen
PollingTextBlock.Text = "Not in Contact";
}
}
}
}
XAML:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="grid">
<Grid.RowDefinitions>
<RowDefinition Height="113*"/>
<RowDefinition Height="655*"/>
</Grid.RowDefinitions>
<ScrollViewer x:Name="scrollviewer" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Visible" Grid.Row="1" >
<Rectangle Fill="#FF3783CF" Height="100" Stroke="#FF33D851" Width="{Binding ElementName=grid, Path=ActualWidth}" Margin="100" StrokeThickness="4" />
</ScrollViewer>
<StackPanel Orientation="Vertical" Margin="45,25,0,0">
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Event Called:" VerticalAlignment="Top" FontSize="24" Margin="0,0,20,0"/>
<TextBlock x:Name="EventCalledTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="24"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Polling Value:" VerticalAlignment="Top" FontSize="24" Margin="0,0,20,0"/>
<TextBlock x:Name="PollingTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="24"/>
</StackPanel>
</StackPanel>
</Grid>
</Page>
I stumbled upon this question since I was struggling with a similar problem. I have a ScrollViewer which has several images in it and I wanted to know what images are shown at the moment the ScrollViewer stops moving...
In the end I did used the ScrollViewer.ViewChanged event. This event keeps triggering untill it has finished with scrolling.
I actually am only interested in the last of these events, but since there is no event that triggers only on that particular moment I need to respond to this one and check for myself if this is the appropriate moment to take actions.
I hope this helps.
ScrollViewer.ViewChanged event: https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.scrollviewer.viewchanged?f=255&MSPPError=-2147217396
I think you need to use the PointerReleased event.
Refer to the following link: https://msdn.microsoft.com/library/windows/apps/br208279

Single-line wpf textbox horizontal scroll to end

I have a templated listbox which template among other things contains a wpf textbox too. The data is provided to the listbox through ItemsSource.
The textboxes display filepaths and these are usally quite long. I want when the textboxes are loaded to show the end of the filepaths.
I tried a combination of DataContextChanged event and setting HorizontalScrollBarVisibility (using double.max or getting the real char length) but to no success. The DataContextChanged seems to be the correct event to use as it fires on each setting of the ItemsSource.
Edit:
Here is sample code to show when the suggestion by Lester works and when it doesnt. I am trying to have it work when the text is set through binding.
<Window x:Class="WpfAppTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="LoadedHandler">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Name="tbb" Width="50" Height="20" Text="{Binding Path=Str}"
IsReadOnly="True" Grid.Column="0" Grid.Row="0"
DataContextChanged="ContextChangedHandler"/>
<ListBox SelectionMode="Single" x:Name="listBox" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="50" Height="20" Text="{Binding Path=Str}"
IsReadOnly="True"
DataContextChanged="ContextChangedHandler"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var obj = new SomeClass
{
Str = "qwetyuiuropqo[psdal;dkas;ldamzxn m,cnz128391"
};
listBox.ItemsSource = new List<SomeClass> { obj };
tbb.DataContext = obj;
}
public class SomeClass
{
public string Str { get; set; }
}
private void LoadedHandler(object sender, RoutedEventArgs e)
{
var obj = new SomeClass
{
Str = "qwetyuiuropqo[psdal;dkas;ldamzxn m,cnz128391"
};
listBox.ItemsSource = new List<SomeClass> { obj };
tbb.DataContext = obj;
}
private void ContextChangedHandler(object sender, DependencyPropertyChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
textBox.CaretIndex = textBox.Text.Length;
var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex);
textBox.ScrollToHorizontalOffset(rect.Right);
}
}
This code worked for me for scrolling to the end of the TextBox (taken from this question):
textBox.CaretIndex = textBox.Text.Length;
var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex);
textBox.ScrollToHorizontalOffset(rect.Right);
Solution is to change DataContextChanged event with Loaded so that proper notifications are received for the textbox.

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);
}
}

Categories