WPF Show Menu in Keyboard Navigation Mode - c#

Backstory aka My Configuration
Like many, I have an application which has a Menu docked to the top. I wanted this menu to be hidden until I pressed the alt key, where I'd then be able to navigate this menu either using my keyboard or by clicking my way through the MenuItems.
In my ViewModel, I made a boolean property called ShowMenu, an ICommand called ShowMenuCommand which operates as a flip flop for ShowMenu, and proceeded to wire things up with data binding. You can see that here:
ShowMenuCommand = new RelayCommand(
_ => ShowMenu = !ShowMenu);
The Menu is set up as such:
<Menu Name="MainMenu" DockPanel.Dock="Top"
Visibility="{Binding ShowMenu, Converter={StaticResource BoolToVis}}"
LostFocus="MainMenu_OnLostFocus">
I also configured keybindings for left and right alt that fire the ShowMenuCommand.
<Window.InputBindings>
<KeyBinding Key="F1" Command="{Binding AboutCommand}" />
<KeyBinding Key="LeftAlt" Modifiers="Alt" Command="{Binding ShowMenuCommand}" />
<KeyBinding Key="RightAlt" Modifiers="Alt" Command="{Binding ShowMenuCommand}" />
</Window.InputBindings>
This works exactly as I configured it to work: The menu is normally hidden, but when I press alt it appears and allows me to navigate the menu items until I click away or I select an item and it loses focus, the LostFocus handler setting ShowMenu to false.
The Problem
Having done this, I seem to have lost the ability to enter Keyboard Navigation Mode. For those who don't know what I mean, normally when you press alt in a gui app, certain characters become underlined, and you can press those characters on your keyboard to navigate the UI. I don't know the formal name for this, so bonus points to anyone who can provide the actual name.
Sources for getting this far
How to make WPF menu bar visible when ALT-key is pressed
LeftAlt Keybinding in WPF
What Now?
I've been searching high and low, crawling through keyboard classes and UIElement in hopes of finding something to fix this, and I've come up with a couple possible solutions. That said, the reason I have resorted to StackOverflow is because I don't know how to pose my google search in such a way that I find what I'm looking for.
My proposed solutions are as follows:
Modify ShowMenuCommand to toggle keyboard navigation mode in addition to modifying visibility.
Remove my keybindings and wire the menu's visibility to whether keyboard navigation mode is enabled.
I thought I would've had this with UIElement#IsInputMethodEnabled, however this does not appear to be the case. That said, I don't know if it matters what element you select, and I don't remember if I tried targeting the Menu or the Window.
If anyone else has any third idea or might know something I'm missing, please do let me know. Hiding the menu bar until you unhide it with alt should be something terribly trivial to set up, so I'd not be surprised if I'm missing something.
Additional Context
If you would like to see any code surrounding the provided snippets, you can view the project source code on GitHub.

So after some additional research and a some help from #Vlad, the final solution is the following:
Menu visibility is bound to ShowMenu property in View Controller.
Window has KeyUp event handler which watches for the alt key.
private void MainWindow_OnKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.System && (e.SystemKey == Key.LeftAlt || e.SystemKey == Key.RightAlt))
{
MainWindowViewModel mwvm = (MainWindowViewModel)DataContext;
mwvm.ShowMenu = !mwvm.ShowMenu;
}
}
This works, except the menu will remain visible after an item is selected. I tried creating an item template to take care of it all at once, but it didn't want to fire.
Handle Click event on relevant menu items, skipping those that are just categories.
The only thing this doesn't take care of is when the user clicks away from the menu. I tried attaching a LostFocus event to the Menu, however this event fires when the menu opens, perhaps because the focus is being taken away from the Menu itself and onto the ContextMenu provider or whatever actually handles drawing the open menu.
I'll need to do some more research and some code cleanup, but the above solution works relatively okay.
Edit: I'll leave this answer unselected for a couple days just in case anyone else has any other ideas.
Edit 2: I found a solution for my use. Since I only have one top-level menu item on this menu, I hooked the MenuItem's SubmenuClosed event. When this happens (either because of a user selecting an option or them clicking away), it hides the menu by setting ShowMenu to false.
It's probably not the most elegant solution, but it's functional. I'll look into cleaning it up later.

Related

Custom OSK: Listen to TextBox Focus

I have written a custom OnScreen Keyboard as an UserControl to have a better control over what the user can type (Alphanumeric/Numpad/Navigation Keys - stuff like that) and to have a better control over the screen layout at design time.
The OSK works by manipulating the text- and selection-properties/functions of a textbox-control.
My main Problem is how to find the right TextBox to inject text into.
My first, naive approach was to register every TextBox I want to use with the OSK Control manually and use the GotFocus/LostFocus of those registered TextBoxes to determine the active control:
public void RegisterInput(TextBox text) {
if (!_listeners.ContainsKey(text)) {
_listeners.Add(text, modes);
text.GotFocus += Input_OnGotFocus;
text.LostFocus += Input_OnLostFocus;
}
}
private void Input_OnLostFocus(object sender, RoutedEventArgs routedEventArgs) {
if (_focused == sender) {
_focused = null;
IsEnabled = false;
UpdateKeyboardMode(); // << Updates Keyboard layout (Alphanumerical vs Numpad) based on focused control
}
}
private void Input_OnGotFocus(object sender, RoutedEventArgs routedEventArgs) {
_focused = (TextBox) sender;
IsEnabled = true;
UpdateKeyboardMode();
Bindings.Update();
}
I work with Focus here, because I need to determine which kind of keyboard (full-size alphanumerical vs. short numpad) to display for each TextBox. The _focused TextBox is then used to directly inject the pressed keys into it. In the constructor of my Page which also contains the OSK-control I would call RegisterInput() with a reference of each and every TextBox I defined on the page. This works just fine — if I have those references.
But now I am working with UserControls. That also removes the TextBoxes out of reach for direct referencing, but I could write some kind of VisualTree-Scan after InitializeComponent() to find all references and call RegisterInput() on each reference I found. If I only need to do this once, it isn't a problem (altough it is still ugly).
One step further - ListBoxes with dynamicly changing contents and DataTemplates. Now I'd need to rescan the whole VisualTree explicitly everytime something changes. But how to detect those changes?
The question is: Can I get an event as soon as $any element in my VisualTree gets/looses focus, without knowing all those elements beforehand (thus replacing RegisterInput() completely)? Or can I listen to changes to the VisualTree to rescan all controls and then call RegisterInput() manually for every TextBox I found?
The goal is to get a handler called everytime a GetFocus/LostFocus event on any TextBox/Control in the UI is raised so that I can update the keyboard to either display a full-sized alphanumerical keyboard (for default textboxes) or a shortened numpad (e.g. for textboxes bound to numerical backing fields).
Alternatively: Is there any other way to inject text and call UpdateKeyboardMode() to update the keyboard layout as soon as the selected textbox changes?
Other options I thought about include:
Build a custom control which derives from a TextBox and let it register itself to the OSK. I'll probably resort to this method, if I don't find any better way. But this will destroy support for 3rd party libraries in which my control is not present and thus does not use the "special magical textbox with osk support".
Don't use events at all. Get the currently focused TextBox with the FocusManager as soon as the user presses a key on my OSK and inject text into the focused instance. Problem with this approach is, that it completely destroys the capability to adapt the OSK to different input types (alphanumerical vs only Numpad), because I cannot determine the keyboard type I need before pressing a key.
Rescan the VisualTree with a timer. Won't do that, thats simply too much of a hack.
Use the OnScreen-Keyboard supplied by Win10 IoT. Two problems: It has no designtime support and is displayed above elements, even if the focused element is directly underneath the keyboard (acceptable if neccessary), but I don't know of a way to change the keyboard "layout" between a full-sized alphanumeric keyboard and a shortened Numpad which only contains numbers and some keys. Also it does not allow to use custom keys (e.g. arrow keys for navigation, custom return key handling).
After a discussion in the chat forum, the actual problem isn't to create a Custom OSK control and use that to interact with the TextBoxs but instead, it's "being bound to use custom control" wrapping a textbox everywhere a OSK needs to be shown.
The Solution would be to listen to the OS-OSK events and when they are triggered, pop up the Custom OSK this ways you won't have to wrap a Textbox in a user control and use that throughout your project.
Link to the Documentation: - respond to the presence of the touch keyboard

Application Context Menu in WPF with WindowStyle=None

I've got a very strange problem with a custom WPF window. The window is designed completely in WPF with WindowStyle=None and ResizeMode=NoResize. So I have to reimplement all the basic window handling stuff like moving, resizing and - here is the problem - the application context menu.
Currently I'm calling the context menu in the mouse-event of the app icon and application titlebar as follows:
POINT point=new POINT();
GetCursorPos(out point);
IntPtr handle=GetSystemMenu(new WindowInteropHelper(this).Handle, false);
IntPtr hd=new WindowInteropHelper(this).Handle;
int retvalue=TrackPopupMenu(handle, TPM_RETURNCMD, (int)point.X, (int)point.Y, 0, hd, new IntPtr());
PostMessage(hd, WM_SYSCOMMAND, retvalue, 0);
This is a code snipped you can find all over the net, looks like everyone is doing it this way.
However depending on the events I use to show the menu the following happens:
MouseUp:
Right click on titlebar/app icon: Application context menu appears
Doing another right click without moving the mouse: Somehow a right click event is generated inside the window (while the mouse still points on the titlebar).
Funny part: The Click is translated into the window by window.Left and window.Top. If I place the window correctly, I can open the context menu of a ListView inside of the window while the mouse still points to the tilebar.
MouseDown:
Right mouse down on titlebar/app icon: Application context menu appears
Right mouse up without moving the mouse: Same behaviour, a right click is generated inside of the window at Mouse position + window.Left/window.Top.
I've made a screenshot and measured the pixel distance to check if it's really window.Top/Left.
That's what I've done while trying to solve the problem:
Tested dozens of changes to the Code snipped to show the context menu without effect
I completely removed resize/move code from the window class, no change
Played around with EventArgs.Handled, no change
Enqueued the code snipped in the dispatcher instead of calling it directly in the event, no change
Have you already tried to implement the context menu in the xaml of the custom window? If you are working with Templates you can already integrate it on there or use it in the CustomWindow.xaml
<'ControlName'.ContextMenu>
<ContextMenu>
<MenuItem Header="FooRoot">
<MenuItem Header="Foo" Command="{Binding FooCommand}"/>
</MenuItem>
<MenuItem Header="Foo2" Click="OnClick"/>
</ContextMenu>
</'ControlName'.ContextMenu>
You can bind the command (maybe if you are working the the MVVM pattern) or make an event for it.

Disabling Dialog Flashing C#

I have a populated ListView dialog. If a user clicks on an option in the ListView, a new dialog is shown above the ListView.
My problems is that when I click off of the new top-most dialog (onto the ListView behind it), the new dialog's borders flash/blink several times. The icon on the taskbar also flashes. I wish to disable the flashing, but cannot find a property to change.
To show my dialog, I use the following code:
if (detail == null)
detail = new Details(opt, val, user, desc, m_l);
else
detail = null;
detail.ShowDialog();
This is intended behavior, it's because the new dialog is modal. It's drawing attention to the fact that something needs to be done.
If you need to make a non-modal form, instead of using ShowDialog(), simply use Show().
Sounds like to me you are creating modal windows each time. And you cannot resume the previous dialogs until you dismiss your new top-most window.
Take a look at this wikipedia article for information about modal dialogs.
I would advise you look at how you are creating/showing your windows.
In WPF you show windows via Show() or ShowDialog(), however, I do not know which type of ListView you are using
EDIT:
Per your comment, you want modal dialogs. The only ways I can think of even trying to remove the flashing is going into WINAPI. This doesn't seem like a job for .NET.
I want to suggest a few things:
Take a look at options for showing each window. See this MSDN page
Take a look at the options for styling each window. See this MSDN page
Reconsider your design. I know this may take a lot of work, but having so many layers of windows is kind of unappealing to most users. Ultimately, I believe this option will make your application the best.
Thank you all for your answers and guidance. I have found the best way to handle my problem.
I was using an event ItemActivated. This event was called when an a highlighted item on the ListView was clicked. This became a problem when the user would double click on an already selected item. This would cause the new dialog to show, but also flash several times.
By using the DoubleClick event instead, a single click on a selected object does nothing. A double click on either a selected or non-selected item opens the dialog without the flashes. The flashes still appear if you try to click off of the dialog box, but are not as much of an issue.

short keys for menu not working in wpf in prism

My application is developed using C# (WPF and Prism). It is having 4 projects in it and different regions for each project:
menu region contains menu view
Drawing Region contains drawings
Tree region contains tree view
Status Region contains satus view
I done binding of shortcut keys for menu and it works fine but for that I need to select a menu header first, for example if I want to open new dashBoard window on CTRL+N first i need to select New Menu on the menu bar. If you have a look at other applications like Word, Notepad etc. if you press CTR+N it opens new file and you need not require to go at new menu.
Is it due to different regions in prism?
There is nothing wrong with the code. It is simple input binding code is something like this:
<UserControl.InputBindings>
<KeyBinding Key="A" Command="{Binding AddDashBoardCommand}">
</KeyBinding>
</UserControl.InputBindings>
Is it due to when I press short keys m in different region n m expecting other region to respond? What is the solution for this?
Based on my understanding, it may be related to the focus on a particular region. Anyway, a possible solution that would work is by using Windows's InputBindings, instead of defining the ShortKey on a specified Region.
This way, the Input Binding would take action anytime you would have focus on the Window Application, without needing to open the New menu. However, you would need to take into account that the Command Binding would yield on the Shell's ViewModel.
So, if you would need to delegate the action to a particular ViewModel, you could Publish() an Event through EventAggregator, which its type would depend on the action made by the user (NewWindowEvent, CopyEvent, SaveEvent, ...). And therefore, each corresponding ViewModel would then suscribe to the particular Event/s that it would only know how to handle it.
You should be able to use InputBindings with or without Modifiers attribute.
Regards.
Please try to use it as below:
<UserControl.InputBindings>
<KeyBinding Key="A" Modifiers="Control" Command="{Binding AddDashBoardCommand}">
</KeyBinding>
</UserControl.InputBindings>
So, using above (added Modifiers property): shortcut Ctrl+A should work for Add Dashboard command.
BTW, I would recommend changing your Add Dashboard shortcut to Ctrl+D, as Ctrl+A is already used for selecting all text.

Raise PropertyChangeNotification on Property in different class (different ViewModel)

Motivation:
I'd like to have a 'File->Save As' MenuItem that behaves just like in Visual Studio. When there is nothing opened it says "Save Selected Items as..."
and when a particular file (e.g. SomeFile.cs) is opened in a tab, the MenuItem reads "Save SomeFile.cs as...".
My App architecture (MVVM, using MVVM Light):
MainWindow.xaml:
<MenuItem Header="{Binding SelectedProjectName}" HeaderStringFormat="Save {0} As..." />
MainWindowViewModel:
I hold a collection of opened tabs (opened files)
private ObservableCollection<BaseProjectViewModel> _projects;
I have a property returning a currently selected tab
public BaseProjectViewModel SelectedProject
{
get
{
return _selectedProject;
}
set
{
if (_selectedProject == value)
{
return;
}
_selectedProject = value;
RaisePropertyChanged("SelectedProject");
RaisePropertyChanged("SelectedProjectName");
}
}
I created a property returning the name of the file in the currently selected tab
public string SelectedProjectName
{
get
{
if (SelectedProject == null)
{
return "Selected Item";
}
return SelectedProject.SafeFileName;
}
}
BaseProjectViewModel serves as a base class for various file types. Each file type has its own class derived from BaseProjectViewModel. It has properties like for example
PaneHeader that returns a string to be displayed in pane header,
SafeFileName that returns just the file name of a path etc...
Question:
When I change the name of the file (thus changing properties of the BaseProjectViewModel) how do I trigger RaisePropertyChanged of the SelectedProjectName in MainWindowViewModel?
What is the cleanest way to do that?
My thoughts
I thought of two possible ways to do that, but i don't know if any of them is the correct way to do it:
(In short) Listening to CollectionChanged on _projects. When there is add/remove -> subscribe/ubsubscribe an event handler that would
look if the PropertyName is the one we are looking for and if yes subsequently call RaisePropertyChanged("SelectedProjectName")?
Use something like MVVM Light Messaging.
Question 2: If you don't suggest any other way and in fact you'd suggest one of these two - could you please elaborate on advantages and disadvantages?
EDIT
I created a very simple project to demonstrate the issue - LINK.
When you run the project:
'New' adds a new TabItem. When text is edited, the TabHeader is decorated with an asterisk.
'Save {0}' menu item "saves" the selected TabItem (simulated by removing the asterisk). I didn't want to complicate the example and introduce a SaveFileDialog and such.
'Save As {0}' menu item simulates Save as in such a way that it adds 'X' character to the end od Tab header string.
When no TabItem is selected, the {0} resolves to "Selected Item".
When you have one tab selected, you click SaveAs() and open the menu, you'll notice that change has not been raised on SelectedProjectName property. When you click another tab and then select the first one back, the change is propagated.
Edit for Erno: What I fail to understand is this: Let's suppose I have a special menu for each document type. Let's suppose I have one particular tab selected (with it's own menu enabled/visible, the other collapsed). How is it going to help me propagate the PropertyChanged of PaneHeader property in BaseProjectViewMode to SelectedProjectName in MainWindowViewModel? If you have time could you please demonstrate it on the example? I also would like to ask you what would be an alternate way if I wanted/neede to do the wiring? Thank you in advance.
From your options I dislike #1 because it might introduce a lot of wiring that is hard to track and maintain.
Option #2 might be OK but could end up in the same wiring mess as #1 but because of the messaging it will be less visible.
I'd consider a different approach:
put a menu in the MainWindow that is responsible for handling commands when no files are open or selected.
when a document is opened in a view and has the focus: replace the current menu with the document specific menu. (like MDI applications work in WinForms)
This way you can customize the Menu per document (type) and it does not require the event dependencies.

Categories