Reputation: 13
I have this code:
private void scrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (Keyboard.IsKeyDown(Key.LeftAlt))
{
e.Handled = true;
//initiate zoooom!
}
}
I wanted to implement alt+mousewheel for zooming reasons but I don't want to lose the regular mousewheel up/down on my scrollviewer. I just wanted in to disable it temporarily when zooming. I've tried unhandling at keyup and at the end of the of the method but still no luck.
The code above works like a toggle thing something. Alt+mousewheeling stops the scrollviewer from scrolling up/down but upon the release of alt the scrollviewer is still locked. It requires a second press of the alt to release the thing.
Upvotes: 1
Views: 824
Reputation: 392
The issue you are having is pressing Alt in a WPF application causes an in-built behaviour to move focus from the current item and into the Access Key Manager. To see this behaviour press Alt then Space and you will see your system menu pop up.
To see it more clearly add a button to your window with the content having an underscore before the character you would like to be your "access key"
<Button Content="_Test" />
Then press Alt and you will see the button text change to have the letter T underscored. Then if you press T the button will be pressed. This is accessibility in action.
The simplest and quickest way to fix your code is to just disable accessiblity by marking the Alt key as Handled every time it is pressed via the KeyDown event or PreviewKeyDown event of the Window.
NOTE: This will disable all accessibility in your WPF window and I would not recommend it.
WPF Window
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
KeyDown="Window_KeyDown"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer x:Name="ScrollViewer">
<Border Background="Blue" Height="5000"></Border>
</ScrollViewer>
</Grid>
</Window>
Code Behind
using System.Windows;
using System.Windows.Input;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.System && (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)))
e.Handled = true;
}
}
}
Note that we basically hook into the key down event of the window to capture all keys, then we listen out if the key is a System key (which could include function keys like F1, F2 etc...) and if so they we check if either Alt key is pressed.
We do the first check to speed up the logic a little and not checking for Alt keys unless we know it is a System key first. Otherwise every key you press on your entire keyboard such as typing in a text box would involve the check for both Alt keys and slow things down.
In order to achieve your desired effect, really you need to be able to detect if your user is in your "mode" or "focus" where when the user begins to press Alt they are in the mode that you wish to disable the Access Keys.
A simple way to determine this mode would be for example only disable Alt when the scroll viewer is focused. Then your logic would change to have one additional check in it like so.
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.System && (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))
&& e.OriginalSource == ScrollViewer) // <-- Added this check
e.Handled = true;
}
Then you will only disable the Alt when the view has focus (has been clicked into for example).
Your challenge becomes harder if you want to detect the mode (to disable Alt action) after the Alt key has already been pressed, such as detecting it once scrolling starts. The reason for that is as soon as Alt is pressed the access key behaviour starts (on key down) so then removing that behaviour after the fact is more difficult.
So after writing the above I figured it's best to give you an example of after-alt style detection. So here it is. This is only a slightly thought out and tested example and may well be buggy (mainly the Task.Delay(0)
relying on the processing of previously released System Key message before focusing) but seems to work ok.
Now you can press Alt as normal, as well as press Alt then start scrolling, and after release the access keys are defocused.
WPF Window
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
KeyDown="Window_KeyDown"
KeyUp="Window_KeyUp"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open"/>
<MenuItem Header="_Close"/>
<MenuItem Header="_Save"/>
</MenuItem>
</Menu>
<ScrollViewer x:Name="ScrollViewer" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<Border Background="Blue" Height="5000"></Border>
</ScrollViewer>
</DockPanel>
</Window>
Code Behind
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace WpfApp1
{
public partial class MainWindow : Window
{
private bool mHasScrolledWithSystemKeyDown = false;
private bool mSystemKeyIsDown = false;
private IInputElement mLastFocusedControl;
public MainWindow()
{
InitializeComponent();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
// If the system key is down...
if (e.Key == Key.System)
// Track it
mSystemKeyIsDown = true;
}
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
// If the system key is down while scrolling...
if (mSystemKeyIsDown)
{
// If it is the first scroll since it being down...
if (!mHasScrolledWithSystemKeyDown)
// Remember currently focused item
mLastFocusedControl = Keyboard.FocusedElement;
// And flag as scrolled with system key down
mHasScrolledWithSystemKeyDown = true;
// Prevent scroll...
e.Handled = true;
// TODO: Zoom
}
}
private void Window_KeyUp(object sender, KeyEventArgs e)
{
// On system key up...
if (e.Key == Key.System)
{
// Track it
mSystemKeyIsDown = false;
// If we had scrolled with the system key down...
if (mHasScrolledWithSystemKeyDown)
// Cause a small delay to allow this key up to process
Task.Delay(0).ContinueWith((t) => Dispatcher.Invoke(() =>
{
// Then focus the last control to "close" the system menu gracefully
mLastFocusedControl?.Focus();
}));
// Flag the has scrolled to false to start again
mHasScrolledWithSystemKeyDown = false;
}
}
}
}
Just a note that if you pursue a method of trying to detect after Alt is pressed to then negate the action, be careful if using any message pump commands like SendMessage as simply sending an VM_KEYUP command for Alt will not work as the button that is physically pressed over and over while it is held, and there are many quirks with the message pump too that you really need to know what you are doing to not break other things.
Upvotes: 2