I want to do a map editor for a game. Its a program that will have Windows Forms UI (like propertyGrid to edit object's properties) but it will also have a panel on which map will be drawn.
What i want:
When focus is on the panel with the map, i'd like to use keyboard to move map around (arrow keys), add objects (number keys) etc. When focus isnt on this panel, i'd like the buttons to work as normal in windows forms - allow to tab between controls etc.
My form looks like this:
It has a ToolStripControl that has a menuStrip (for main menu) and a statusStrip (for status bar). In the middle of the form (or toolstripcontrol), SplitControl is docked (dock=fill) that has two panels. Panel 1 has the PanelMap - a Panel that displays the map, Panel 2 has all other stuff like propertygrid, tabcontrols, buttons etc.
I have KeyPreview of form set to true and process keyboard events in form's keydown event handler.
Now, what happens is if i assign focus to PanelMap, next time i press an arrow key, NO KeyDown event fires. Not a single one! Even form which is supposed to process all events because it has "KeyPreview" doesnt get its even to fire. When i press an arrow, PanelMap loses focus towards the SplitControl.
Okay, i thought, maybe PanelMap is not supposed to ever have focus, lets give focus to SplitControl (if i press arrow key while it has focus, i can handle it so it doesnt go further). But then, if anything like a textbox that is inside something that is inside SplitControl has focus, then SplitControl CANNOT get focus. .Focus() will do nothing - focus remains in the whichever control that had it!
Why does it act so strange? Why doesnt Form's KeyDown fire when panel has focus and arrow key is pressed? Why doesnt SplitControl get focused when i call .Focus() even though CanFocus=true?
And ultimately, how do i achieve what i want? Is there a way to do it?
I think you're running into the widgets taking the keystrokes before your events get to them for navigation. I had this issue, and did this:
private void RemoveCursorNavigation(Control.ControlCollection controls)
{
foreach(Control ctrl in controls)
{
ctrl.PreviewKeyDown += new PreviewKeyDownEventHandler(MainWin_PreviewKeyDown);
RemoveCursorNavigation(ctrl.Controls);
}
}
I call this function in the main form's Load handler, like this:
RemoveCursorNavigation(this.Controls);
In your PreviewKeyDown handler, you need to do this:
public void MainWin_PreviewKeyDown(Object sender, PreviewKeyDownEventArgs e)
{
switch(e.KeyCode)
{
case Keys.Up:
case Keys.Down:
case Keys.Left:
case Keys.Right:
e.IsInputKey = true;
break;
default:
break;
}
}
The e.IsInputKey = true; tells the outside that you've used this event, and don't want it going anywhere else.
Now you get to see the keystrokes before they go to the widgets, and you won't get navigation between them from the cursor keys.
I found an answer like this:
I made a textbox that is hidden under a panel (but enabled and visible). This textbox is given focus when i want to "lock" focus on my PanelMap. It has onkeydown even with e.suppress = true so that textbox never gets any keystrokes to affect it.
Crude workaround but works wonders... typical M$ business...
Related
What is the difference(s) between GotFocus and GotKeyboardFocus -and similarly LostFocus and LostKeyboardFocus?
Sorry for the simple question, but, I googled it and read a lot of blog posts, but I'm still confused. It seems nobody knows exactly what is the difference ):
UPDATE:
My usage:
I am creating a custom control by extending Control class. Something like ComboBox but with some other effects. I'm trying to open and close a Popup by setting a property: IsDropDownOpen just like a ComboBox through the GotFocus and LostFocus events. I don't want to Popup get closed, when I Alt+Tabed the windows, but get closed when I click on a Button for example or I go to a TextBox. I did:
private static void OnGotFocusHandler(object sender, RoutedEventArgs e) {
if (e.Handled)
return;
((SearchBox)sender).IsDropDownOpen = true;
e.Handled = true;
}
private static void OnLostFocusHandler(object sender, RoutedEventArgs e) {
if (e.Handled)
return;
((SearchBox)sender).IsDropDownOpen = false;
e.Handled = true;
}
The GotFocus works. But the Lost one didn't. If I do the Lost stuff in LostKeyboardFocus then when I Alt+Tab the windows, or Window goes to inactive, then the method get called, while I don't want. How can I solve it?
MSDN has an overview of focus, but I'll try to explain it here.
WPF has 2 concepts regarding focus. There is the physical keyboard focus, and there is logical focus. Only one element can have keyboard focus (and if the application isn't the active application, no element will have keyboard focus).
Multiple items can have logical focus. In fact, you can create new "focus scopes". As per MSDN:
When keyboard focus leaves a focus scope, the focused element will lose keyboard focus but will retain logical focus. When keyboard focus returns to the focus scope, the focused element will obtain keyboard focus. This allows for keyboard focus to be changed between multiple focus scopes but ensures that the focused element in the focus scope regains keyboard focus when focus returns to the focus scope.
You can define your own focus scope on an element (typically a Panel) by setting FocusManager.IsFocusScope="True". The controls in WPF that are focus scopes by default are Window, MenuItem, ToolBar, and ContextMenu.
This makes sense if you think about having multiple Windows in your application. When you Alt-Tab between them, you expect your keyboard focus to return to the same place it was the last time the Window had focus. By keeping keyboard focus and logical focus separate, you can achieve this.
Having the following WinForms dialog form, I am handling the GotFocus event of MyControl:
MyControl derives from the DevExpress XtraUserControl which in turn derives from the Microsoft WinForms standard UserControl.
What I want to achieve is that when MyControl gets the focus when the user navigates with the Tab and MyControl gets the focus, that the focus is forwarded to the child controls.
I do this successfully with the following code:
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
// Forward.
foreach (Control control in Controls)
{
if (control.TabStop)
{
control.Select();
break;
}
}
}
I.e. if Button 1 is focused and the user presses the Tab key, the focus is set to Button 2.
What I'm not able to solve is if the user navigates backward. I.e. if Button 4 is focused and the user presses the Shift+Tab keys, the focus should be set to Button 3.
My two questions are:
Is there a way to detect the navigation order of the user inside the GotFocus event?
Am I doing it the right way at all? Maybe there is a built-in function/flag I can set to MyControl to automatically forward the focus to its child controls?
So many possibilities:
use the OnLostFocus event to store the current control and calculate whether TAB or SHIFT TAB was pressed
override ProcessKeyPreview to calc the action to be performed in OnGotFocus (SO answer)
override ProcessCmdKey as in this answer
I have a Form which covers the entire screen. I have a textbox on it which is normally hidden but appears when a user clicks, drags mouse and then leaves. After that user can enter any value in the text box. Once entered, ideally user should be able to click outside of textbox and then the normal service should resume.
By normal service I mean that form should start getting all the events. What I have done so far is that on TextBox's KeyDown event; when Escape key is pressed, I have set the focus to the main form like this:
this.Focus(); //where this is mainform.
But this doesn't seem to work since Textbox still keeps receiving all the keys. I have a KeyDown event handler both for Form and Textbox and I have checked that all the KeyDown events pass on to the TextBox. I have a TextBox Leave event Handler as well which never gets called.
This TextBox is the only control on the form and the main form is used for drawing shapes (if that matters).
So, how can I make this TextBox lose focus when user clicks outside of it.
if it works like in VB, for what I remember, try to set the form property KeyPreview to false so all keys will be passed only to the focused control on the form.
If you set your form KeyPreview property to true, your form has first chance to handle any keystrokes that you make. If it is something you want to handle i.e. escape as in your comment above, handle it in your form's KeyDownEvent and mark it as handled so your textbox will not see it.
From above Msdn Page:
When this property is set to true, the form will receive all KeyPress, KeyDown, and KeyUp events. After the form's event handlers have completed processing the keystroke, the keystroke is then assigned to the control with focus.
I guess back in '11 this didn't work, but now
this.ActiveControl = null;
works fine. However if you intend to use Tab to cycle controls, focusing a label with suitable TabIndex is the way.
I would like to be able to have a user be able run through the tabs, setting focus to each one, but only when they hit enter, the tabpage will render.
You would think that the paint event would be involved, but I don't know how to "cancel out" of it, if that would even do the job..
First, I should caution you that you're overriding the standard Windows behavior. In any property page dialog or anywhere else that uses tabs in the user interface, using the left and right arrow keys will flip through the tabs and cause them to display their contents in the tab control. You do not have to press Enter to get the selected tab page to display. Make sure that your users understand that your application is different (and that you understand the needs of your users) if you decide to go this route.
That said, you can override this behavior by handling the KeyDown event for the TabControl, detecting when one of the arrow keys has been pressed, and cancelling it. For example:
private void myTabControl_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
//Check to see if an arrow key was pressed
if ((e.KeyCode == Keys.Left) || (e.KeyCode == Keys.Right))
{
//Cancel the keypress by indicating it was handled
e.Handled = true;
}
}
However, once you do this, there will be no way for the user to set focus to a particular tab page's tab, because once the tab gets focus, the tab page is immediately brought into view. This is handled by the parent TabControl and is unrelated to the Paint event (which is responsible for how the control gets painted, not when or why).
Of course, you can always determine if the Enter key was pressed in the same KeyDown event and activate any tab page that you wish (such as by using a counter variable that is incremented/ decremented each time the corresponding arrow key is pressed), but there will be no visible indication to the user which tab will then be brought into view. The focus rectangle will not be drawn.
Also be aware that pressing Ctrl+Tab or Ctrl+Page Up/Page Down will switch between tab pages. If this is also undesirable, you'll need to watch for and cancel these key combinations as well.Any time you start trying to override default behaviors, you're in for a lot more trouble than if you just design your application around it. If there's a particular reason you want to require the Enter key to commit tab page switching, we might be able to help you come up with an easier and better solution.
I'm not sure I understand what you are trying to accomplish, but it sounds like you can do it using the Visible property.
You should be able to set the TabPage's visibility to false when the user switches to it, and then set it to true only when you want to.
On my main form, there is another (floatable) window. This floatable window works sort of like a popupwindow in that it will close when the user clicks somewhere else outside of this window. This is handled by the Deactivate event. But what I want to do is, if the user clicks on a different control (say a button), I want to both close this float window and then activate that button with just one click. Currently, the user has to click twice (one to deactivate the window and once more to activate the desired button). Is there a way to do this with just one click?
foreach(Control c in parentForm.Controls)
{
c.Click += delegate(object sender, EventArgs e)
{
if(floatyWindow != null && floatyWindow.IsFloating)
{
floatyWindow.Close();
}
};
}
And then add your handlers as normal. This additional handler can close the floaty window.
Make sure you floaty window isn't a dialog too as this will not allow your parent form's controls to be clicked.
I had a slightly hacky solution. In your Deactivate event, fire another custom event to your main form. Then when you main form is handling the custom event, enumerate through your control(this.Controls) and locate the control under the mouse by checking all their bound then call Focus(). You might need to sort by the one with the smallest surface area, or you can have a separate list of "focus-able" control like button just for this purpose.
Another way might be to switch focus to your main form immediately after OnMouseLeave of the floatable window, or OnMouseHover of your main window, but keep the floatable windows on top, just no focus. Handle the global mouse down of your main form, and close the floatable window by then.
These are just theories, not tested.
I had an issue like this once too, when a customer wanted "floaty" windows all over there application. I used used an approach similar to the one described in this article:
http://www.vbaccelerator.com/home/NET/Code/Controls/Popup_Windows/Popup_Windows/article.asp
Code sample available here:
http://www.vbaccelerator.com/home/NET/Code/Controls/Popup_Windows/Popup_Windows/Popup_Form_Demonstration.asp
By extending this a bit we created "floaty" windows similar to the ones VS uses when you get a runtime error while debugging code.
At the very least reading the code may give you some insight, however, quarrelsome's response may be the more simple solution.