I would like to enable holding down SHIFT on the keyboard while scrolling to scroll a ScrollViewer horizontally.
I learned from here that the PointerWheelChanged event is the one I am looking for. Handling that, however, doesn't work because the ScrollViewer handles it internally, so my handler is never called. To get around that, I used the AddHandler method, as described in the "Routed Events Overview" article.
This works... but appears to be running my code AFTER the ScrollViewerruns its internal code. The result of this is that the ScrollViewer content pans vertically, then horizontally. They appear to happen in that order, and setting e.Handled = true doesnt stop it.
Is there a way to "intercept" the scroll, so I can handle it with my own logic, thus allowing the ScrollViewer to pan horizontally if SHIFT is pressed? I recently asked a similar question (involving intercepting input to a control so I could handle it with my own logic) here, where the answer involved handling a different event, where that event took place prior to control running its own logic. I do not see a similar "pre stuff happening" event for the pointer scroll.
The code I have follows. Note that the ScrollViewer can scroll BOTH horizontally and vertically, as well as zoom:
<!-- Contained in Grid in a UserControl, if that's relevant -->
<ScrollViewer Name="MyCanvasScrollViewer"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
ZoomMode="Enabled"
ZoomSnapPointsType="Optional"
PointerWheelChanged="MyCanvasScrollViewer_PointerWheelChanged">
<!-- Content to pan -->
</ScrollViewer>
Code-behind:
// Constructor for the user contol.
public MyControl()
{
// Add the scroll wheel event handler and force it to run.
this.MyCanvasScrollViewer.AddHandler(ScrollViewer.PointerWheelChangedEvent, new PointerEventHandler(this.MyCanvasScrollViewer_PointerWheelChanged), true);
// Other un-related stuff omitted here...
}
// Event handler for the Pointer Wheel Changed event.
private void MyCanvasScrollViewer_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
// If SHIFT is pressed...
var keyState = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Shift);
if ((keyState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down)
{
// Get the amount to scroll.
PointerPoint pointer = e.GetCurrentPoint(this.WallCanvasScrollViewer);
double scrollWheelDelta = pointer.Properties.MouseWheelDelta;
// Change the view in the scroll viewer.
this.MyCanvasScrollViewer.ChangeView(scrollWheelDelta, null, null, true);
// Mark event as handled.
e.Handled = true;
}
}
You could simply disable VerticalScrollMode in shift keydown and enable it on keyup. No need for pointerwheelchanged itself. It works perfectly fine.
Xaml
<ScrollViewer ZoomMode="Enabled" x:Name="MyScrollViewer" HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" >
<Image Height="600" Width="500" Source="/Assets/1.jpg"></Image>
</ScrollViewer>
//C# code
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
CoreWindow.GetForCurrentThread().KeyDown += MainPage_KeyDown;
CoreWindow.GetForCurrentThread().KeyUp += MainPage_KeyUp; ;
}
private void MainPage_KeyUp(CoreWindow sender, KeyEventArgs args)
{
if (args.VirtualKey == VirtualKey.Shift)
{
MyScrollViewer.IsScrollInertiaEnabled = true;
MyScrollViewer.VerticalScrollMode = ScrollMode.Enabled;
}
}
private void MainPage_KeyDown(CoreWindow sender, KeyEventArgs args)
{
if (args.VirtualKey == VirtualKey.Shift)
{
MyScrollViewer.IsScrollInertiaEnabled = false;
MyScrollViewer.VerticalScrollMode = ScrollMode.Disabled;
}
}
}
Related
I'm trying to get my border focused after the user clicks on it.
Currently it is possible to focus the border via tabs, but via click would be way more convenient for the user.
<Border x:Name="BorderFileInfo" Focusable="True" BorderBrush="LightGray" BorderThickness="1">
<Grid Margin="3,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left">
<!-- CONTENT CTRL -->
</Grid>
</Border>
I saw in another post that there is a possability to catch the click event with an InputBinding but I don't know how to focus the border afterwards without using a command.
Stackoverflow: Why doesnt WPF border control have a mousedoubleclick event?
Is there an easy way to do that other than having to create commands ?
The app is pretty small so I don't want to use commands if I don't have to.
One easy way is to handle PreviewMouseDown or similar mouse events and set the focus:
private void Border_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Keyboard.Focus(sender as Border);
}
edit
note that you can create Click by handling PreviewMouseLeftButtonDown and PreviewMouseLeftButtonUp in this way:
_isdown =false;
private void Border_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isdown =true;
}
private void Border_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if ( _isdown)
{
_isdown = false;
Keyboard.Focus(sender as Border);
}
}
Background:
I have a usercontrol defined in a ScrollViewer along with a ContentControl, the ContentControl will be visible all the time, and within it there is a Button, when the button is clicked will set the usercontrol to Visible, and when the usercontrol shows (Visiblility="Visible") I want it to be scrolled into the view. I have
XAML
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="465">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding MyOtherViewModel}" Width="960" ></ContentControl>
<local:MyView IsVisibleChanged="MyView_IsVisibleChanged" Grid.Row="1" Visibility="{Binding IsNonCompliant, Converter={StaticResource BooltoVisible}, UpdateSourceTrigger=PropertyChanged}" />
</ScrollViewer>
Code Behind
private void MyView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
(sender as Control).BringIntoView();
}
Problem: this is not working, or more precisely, my usercontrol scrolled into the view first then revert back to the bottom of the ScrollViewer in a blink.
Weird thing: show a messagebox before calling BringIntoView will correctly display my usercontrol into the middle of the view
Current hack solution: you can see this works even to close the Window immediately after its loaded
private void MyView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Window ss = new Window();
ss.Loaded += new RoutedEventHandler(ss_Loaded);
ss.ShowDialog();
(sender as Control).BringIntoView();
}
private void ss_Loaded(object sender, RoutedEventArgs e)
{
(sender as Window).Close();
}
Question: I know there must be something else going on, but I just can't identify it, but I really want to know what happened when a window showing with ShowDialog? Is this because it refreshes the window so that the BringIntoView will happen only after the usercontrol been loaded? (Not as the problem I have now: BringIntoView happened first, and then the window get refreshed and put the scrollbar back to the top). And what is the correct fix for my problem?
It looks like BringIntoView called before my Usercontrol getting rendered, as a result when it gets fully rendered, the scrollbar is revert back to the top (as I have described in my question). And thanks for the answer from #Evgeny posted for another question, I get a better solution now (less hack maybe?). Still want to see if there are better solutions.
private void MyView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var border = (FrameworkElement)sender;
if (border.IsVisible)
{
//Window ss = new Window();
//ss.Loaded += new RoutedEventHandler(ss_Loaded);
//ss.ShowDialog();
using (BackgroundWorker bg = new BackgroundWorker())
{
bg.DoWork += new DoWorkEventHandler(bg_DoWork);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
Tuple<FrameworkElement, double> b = new Tuple<FrameworkElement, double>(border, border.Height);
bg.RunWorkerAsync(b);
}
}
}
private void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
(e.Result as UserControl).BringIntoView();
}
private void bg_DoWork(object sender, DoWorkEventArgs e)
{
int maxwait = 300 //not scrolled to the view is not a disaster, but if the program hangs forever it will be a disaster, so set this to prevent that from happening
while (maxwait!=0
&&
(e.Argument as Tuple<FrameworkElement, double>).Item1.ActualHeight != (e.Argument as Tuple<FrameworkElement, double>).Item2)
{
Thread.Sleep(1);
maxwait --;
}
e.Result = (e.Argument as Tuple<FrameworkElement, double>).Item1;
}
I cannot believe that using background worker is a correct solution for this! You can use LayoutUpdated event to easily find when control is loaded and finally displayed.
userControl.LayoutUpdated+=OnLayoutUpdated;
private bool loaded=false;
private void OnLayoutUpdated(object sender,EventArgs e)
{
if (!loaded && (view.ActualHeight > 0 || view.ActualWidth > 0))
{
// Unsubscribe or set a flag.
userControl.LayoutUpdated -= OnLayoutUpdated;
loaded = true;
}
}
So you can execute that code when layout is updated and height or width is set. This will means that control is loaded and displayed.
Than you can unsubscribe or set a flag that initialization is complated.
So let's say I have stackpanel on the left edge with width 200px. Now I want to handle horizontal swipe from left to right on this panel and show additional panel. Then handle swipe from right to left to hide it.
I tried handling page's ManipulationStarted and ManipulationDelta events, but it doesn't seem to have any effect at least with mouse. Any ideas, what could be an easy way to implement that?
What I've tried:
Handle page's swipe events and on the beginning of swipe check, if it was started in bounds of stackpanel, else I ignore it.
If swipe's delta was more then positive 40, looks like swipe was from left to right.
My XAML file:
// standard stuff of page
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Vertical" Width="200" HorizontalAlignment="Left" Background="White" x:Name="Panel"/>
</Grid>
// continue standard stuff
C# file:
public MainPage()
{
this.InitializeComponent();
this.ManipulationDelta += MainPage_ManipulationDelta;
this.ManipulationStarted += MainPage_ManipulationStarted;
}
private void MainPage_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("heh");
if (e.Position.X < 200)
{
initialPoint = e.Position;
isSwiping = true;
}
}
private void MainPage_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
if (e.IsInertial && isSwiping)
{
Point currentPoint = e.Position;
if (currentPoint.X - initialPoint.X >= 40)
{
isSwiping = false;
e.Complete();
System.Diagnostics.Debug.WriteLine("finished swipe :)");
}
}
}
private Point initialPoint;
private Boolean isSwiping;
(Again omitted default empty page)
You need to set ManipulationMode on a control e.g. ManipulationMode="TranslateX" and have the control respond to hit-testing (i.e. if it does not have a background - set the Background to Transparent) to receive manipulation events.
Then again - why not just use a ListView that has built-in support for swipes?
I have several read only RichTextBox's that are used for logging output. Since they're read only they don't seem to automatically scroll when the text is updated. I can use the TextChanged event to force a scroll to end, but is there not simply a way to set a property or something in the XAML so that scrolling happens like normal?
I had googled for your problem and found this post.
In the section "Programming the RichTextBox" author had described about getting the behavior what you had been expecting.
Please check and let me know if it is of any use.
I tried to reproduce your problem and came up with the following solution
<Window x:Class="CheckRichTextBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="170" Width="300">
<StackPanel>
<RichTextBox Height="100" Name="richTextBox1" IsReadOnly="True" VerticalScrollBarVisibility="Visible"/>
<Button Name="btnAdd" Content="Click me to add text" VerticalAlignment="Bottom" Click="BtnAddClick" />
</StackPanel>
</Window>
The code behind for the same is as below:
using System.Windows;
namespace CheckRichTextBox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BtnAddClick(object sender, RoutedEventArgs e)
{
richTextBox1.AppendText("You had Clicked the button for adding text\n");
richTextBox1.ScrollToEnd();
}
}
}
This solves the problem of autoscroll, please check it and let me know if it is of any help.
I solved this problem using an Interactivity trigger and a very simple action.
The action looks like this:
public class ScrollToBottomAction : TriggerAction<RichTextBox>
{
protected override void Invoke(object parameter)
{
AssociatedObject.ScrollToEnd();
}
}
Then in my XAML I have this:
<RichTextBox IsReadOnly="True" VerticalScrollBarVisibility="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<interactivity:ScrollToBottomAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</RichTextBox>
I came up with the following solution for wpf richtextbox autoscroll
public partial class MainWindow
{
private bool AutoScroll = true;
public MainWindow()
{
InitializeComponent();
yourRichTextBox.Loaded += (s, e) =>
{
var scrollViewer = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(yourRichTextBox, 0), 0) as ScrollViewer;
scrollViewer.ScrollChanged += (scroller, eScroller) => ScrollViewer_ScrollChanged(scroller, eScroller);
};
}
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// User scroll event : set or unset autoscroll mode
if (e.Source as ScrollViewer != null && e.ExtentHeightChange == 0)
{ // Content unchanged : user scroll event
if ((e.Source as ScrollViewer).VerticalOffset == (e.Source as ScrollViewer).ScrollableHeight)
{ // Scroll bar is in bottom
// Set autoscroll mode
AutoScroll = true;
}
else
{ // Scroll bar isn't in bottom
// Unset autoscroll mode
AutoScroll = false;
}
}
// Content scroll event : autoscroll eventually
if (AutoScroll && e.ExtentHeightChange != 0 && e.Source as ScrollViewer != null)
{ // Content changed and autoscroll mode set
// Autoscroll
(e.Source as ScrollViewer).ScrollToVerticalOffset((e.Source as ScrollViewer).ExtentHeight);
}
}
}
This is how to do it in C#
Put the RichTextBox in a ScrollViewer like this:
scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
scrollViewer.Content = rTextBox;
Then add the scrollViewer to the Grid or whatever you are using.
RootGrid.Children.Add(scrollViewer);
(This is in C# but could all be done in XAML as well.)
Then use C# code like this to make it scroll to the bottom when you add text:
rTextBox.AppendText(str);
scrollViewer.ScrollToEnd();
Hope that helps.
RichTextBox.AppendText("String")
RichTextBox.ScrollToCaret()
When I was adding to RichTextBox.text, ScrollToCaret() does not work.
RichTextBox.text = RichTextBox.text + "String"
RichTextBox.ScrollToCaret()
Is it possible to implement mouse click and drag selection box in WPF. Should it be done through simply drawing a rectangle, calculating coordinates of its points and evaluating position of other objects inside this box? Or are there some other ways?
Could you give a bit of sample code or a link?
Here is sample code for a simple technique that I have used in the past to draw a drag selection box.
XAML:
<Window x:Class="DragSelectionBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<Grid
x:Name="theGrid"
MouseDown="Grid_MouseDown"
MouseUp="Grid_MouseUp"
MouseMove="Grid_MouseMove"
Background="Transparent"
>
<Canvas>
<!-- This canvas contains elements that are to be selected -->
</Canvas>
<Canvas>
<!-- This canvas is overlaid over the previous canvas and is used to
place the rectangle that implements the drag selection box. -->
<Rectangle
x:Name="selectionBox"
Visibility="Collapsed"
Stroke="Black"
StrokeThickness="1"
/>
</Canvas>
</Grid>
</Window>
C#:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
bool mouseDown = false; // Set to 'true' when mouse is held down.
Point mouseDownPos; // The point where the mouse button was clicked down.
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
// Capture and track the mouse.
mouseDown = true;
mouseDownPos = e.GetPosition(theGrid);
theGrid.CaptureMouse();
// Initial placement of the drag selection box.
Canvas.SetLeft(selectionBox, mouseDownPos.X);
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Width = 0;
selectionBox.Height = 0;
// Make the drag selection box visible.
selectionBox.Visibility = Visibility.Visible;
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
// Release the mouse capture and stop tracking it.
mouseDown = false;
theGrid.ReleaseMouseCapture();
// Hide the drag selection box.
selectionBox.Visibility = Visibility.Collapsed;
Point mouseUpPos = e.GetPosition(theGrid);
// TODO:
//
// The mouse has been released, check to see if any of the items
// in the other canvas are contained within mouseDownPos and
// mouseUpPos, for any that are, select them!
//
}
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
// When the mouse is held down, reposition the drag selection box.
Point mousePos = e.GetPosition(theGrid);
if (mouseDownPos.X < mousePos.X)
{
Canvas.SetLeft(selectionBox, mouseDownPos.X);
selectionBox.Width = mousePos.X - mouseDownPos.X;
}
else
{
Canvas.SetLeft(selectionBox, mousePos.X);
selectionBox.Width = mouseDownPos.X - mousePos.X;
}
if (mouseDownPos.Y < mousePos.Y)
{
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Height = mousePos.Y - mouseDownPos.Y;
}
else
{
Canvas.SetTop(selectionBox, mousePos.Y);
selectionBox.Height = mouseDownPos.Y - mousePos.Y;
}
}
}
}
I wrote an article about this:
https://www.codeproject.com/Articles/148503/Simple-Drag-Selection-in-WPF
You can get this functionality pretty easily by adding an InkCanvas and set its EditingMode to Select. Although it's primarily intended for Tablet PC ink collection and rendering, it's very easy to use it as a basic designer surface.
<Window Width="640" Height="480" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<InkCanvas EditingMode="Select">
<Button Content="Button" Width="75" Height="25"/>
<Button Content="Button" Width="75" Height="25"/>
</InkCanvas>
</Window>
This project created a custom MultiSelector which supports several selection methods including a rectangular "lasso" style:
Developing a MultiSelector by Teofil Cobzaru
It is far too long to reproduce here. The key elements of the design, IIRC, were to create a custom ItemContainer which knows how to interact with its MultiSelector parent. This is analagous to ListBoxItem / ListBox.
This is probably not the simplest possible approach, however if you are already using some type of ItemsControl to host the items which may need to be selected, it could fit into that design pretty easily.
MouseDown logic:
MouseRect.X = mousePos.X >= MouseStart.X ? MouseStart.X : mousePos.X;
MouseRect.Y = mousePos.Y >= MouseStart.Y ? MouseStart.Y : mousePos.Y;
MouseRect.Width = Math.Abs(mousePos.X - MouseStart.X);
MouseRect.Height = Math.Abs(mousePos.Y - MouseStart.Y);