I would like to hide a groupox from my WPF app and to manually enable it when I need it via a keypress code when app is active(not minimized)
this example works sometimes with just one keypress, but I need a sequence
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.B)
{
bdgb.Visibility = Visibility.Visible;
}
}
You could for example override the OnPreviewKeyDown method of your window, e.g.:
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.B)
{
//...
}
}
On what did you put the handler? Only currently focused element gets the notification, and if it is not handled, it goes up the tree.
You need to put it in the most top UIElement, which means your window.
Or, you can do it in a moer MVVMy WPF style, by creating a command binding to the key and having a property on your VM GroupBoxVisible, set it to "True" and have binding to the GroupBox.Visibillity.
Moer details here
Related
I have multiple children of the same WinForms form, each with its own handler for a keyboard event. For a minimal example (C#):
public Form1() {
InitializeComponent();
c1 = new Control();
c2 = new Control();
c1.KeyPress += c1_KeyPress;
c2.KeyPress += c2_KeyPress;
Controls.Add(c1);
Controls.Add(c2);
}
void c1_KeyPress(object sender, KeyPressEventArgs e) {
Text += " c1";
e.Handled = true;
}
void c2_KeyPress(object sender, KeyPressEventArgs e) {
Text += " c2";
e.Handled = true;
}
When the event fires, it always gets handled by whichever child was originally added to the form first. Reordering the children with c2.BringToFront() or Controls.SetChildIndex(c2, 0) doesn't change the priority. Reordering the constructions or the delegate assignments doesn't change anything either. Calling c2.Focus() doesn't either. Changing the order of the Add calls is the only thing that seems to affect it.
(By contrast, for mouse events the priority gets resolved in an expected way: the topmost control under the pointer hotspot gets dibs on the event, and "topmost" is a clear concept I can control using BringToFront and friends.)
In my real case, c1 is a simple custom control derived from WinForms.UserControl, and c2 is a CefSharp.WinForms.ChromiumWebBrowser. There the keyboard events are caught by c2 no matter what I do.
What decides this priority of handlers? How can I change it?
There is no "priority", keyboard events are raised on the control that has the focus. Intuitively simple to understand, entering text in a TextBox requires selecting it first. A very significant flaw in the posted snippet is that you cannot tell which one has the focus. Although the Control class is usable as-is, in practice you almost always need to derive your own class from it to give it desirable behavior.
Add a new class to your project and paste the code shown below. Replace new Control() with new MyControl(). Now you can tell.
using System;
using System.Windows.Forms;
class MyControl : Control {
protected override void OnEnter(EventArgs e) {
this.Invalidate();
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e) {
this.Invalidate();
base.OnLeave(e);
}
protected override void OnPaint(PaintEventArgs e) {
if (this.Focused) {
ControlPaint.DrawFocusRectangle(e.Graphics, this.DisplayRectangle);
}
base.OnPaint(e);
}
}
I created a WPF UserControl, that handles all GotFocus/LostFocus events of its child controls. I call the OnGotFocus/OnLostFocus of the UserControl, but the IsFocused property of the UserControl will not set:
void MyUserControl_Initialized(object sender, EventArgs e)
{
foreach (UIElement control in (Content as Panel).Children)
{
control.LostFocus += control_LostFocus;
control.GotFocus += control_GotFocus;
}
}
void control_GotFocus(object sender, RoutedEventArgs e)
{
if (!IsFocused)
{
e.Handled = false;
OnGotFocus(e);
}
}
void control_LostFocus(object sender, RoutedEventArgs e)
{
bool hasAnythingTheFocus = false;
foreach (UIElement control in (Content as Panel).Children)
{
if (control.IsFocused)
{
hasAnythingTheFocus = true;
}
}
if (!hasAnythingTheFocus)
{
OnLostFocus(e);
}
}
How can I set it?
Instead of the IsFocused you can use IsKeyboardFocusWithin
use the event UIElement.IsKeyboardFocusWithinChanged and it should worked perfectly.
The GotFocus method will be called when the relevant control receives logical focus... from the UIElement.GotFocus Event page on MSDN:
Logical focus differs from keyboard focus if focus is deliberately forced by using a method call but the previous keyboard focus exists in a different scope. In this scenario, keyboard focus remains where it is and the element where a Focus method is called still gets logical focus.
A more precise interpretation of this event is that it is raised when the value of the IsFocused property of an element in the route is changed from false to true.
Because this event uses bubbling routing, the element that receives focus might be a child element instead of the element where the event handler is actually attached. Check the Source in the event data to determine the actual element that gained focus.
it will get focus when the user clicks on the relevant control in the UI and/or when you call control.Focus() in your code. The IsFocused is readonly and cannot be set.
Update: Discusion revealed that the problem is only arising, when you have the custom control text box hosted within a wpf application that is again hosted via elementhost within a winforms application.
I have a WPF-CustomControl inherting from TextBox. I override the OnLostKeyBoardFocus method.
As part of this method I raise an event. One event handler is showing a MessageBox (this is not under my control). When the user closes the MessageBox the KeyBoardFocus is returned to the TextBox, directly. Even though, OnLostKeyboardFocus(...) has still not returned.
The automatic (re)focussing on my TextBox control, causes a whole range of problems for me. Can I circumvent this behavior in some way other than dispatching the event with Dispatcher.BeginInvoke().
class MyTextBoxCustomControl : TextBox {
public event EventHandler<EditCompletedEventArgs> EditCompleted;
private void OnEditCompleted(EditCompletedEventArgs e)
{
var handler = EditCompleted;
if (handler != null) handler(this, e);
}
protected override OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e){
base.OnLostKeyboardFocus(e);
OnEditCompleted(new EditCompletedEventArgs())
//Before this point is reached OnGotKeyboardFocus(...) is called
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
//Is called twice, directly after MessageBox is closed and
//after OnLostKeyboardFocus(...) returns
}
}
class MyEventHandler {
private void Test(){
var myTBCC = new MyTextBoxCustomControl();
//Closing the message box will return focus to myTBCC, which directly
//causes OnGotKeyboardFocus to be called
myTBCC.EditCompleted += (a, b) =>
MessageBox.Show("PressOk");
}
}
You could try call .MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); on sender right after MessageBox.Show.
EDIT:
...
MessageBox.Show("PressOk");
((MyTextBoxCustomControl)a).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
...
For a very specific reason I want to select ListViewItems on mouse button up, not actually on mouse button down. I want this behaviour to be embedded in the control. Is it possible to achieve this? can anyone give hint?
Yes it's definitely possible using attached properties. Define an attached property called SelectOnMouseUp and when it's set to true, hook to your ItemsContainerGenerator events to discover when a new item container is added. Then when you get an event for a new item container, hook into its PreviewMouseDown and ignore it (set e.Handled to true), and hook into its MouseUp event and perform the selection (set IsSelected to true).
Aviad P.'s answer is a good one and a clever use of attached properties, but I tend to use a different technique most of the time:
Subclass ListViewItem.
Override OnMouseLeftButtonDown and OnMouseRightButton to do nothing.
Override OnMouseLeftButtonUp / OnMouseRightButtonUp to call base.OnMouseLeftButtonDown / base.OnMouseRightButtonDown.
Subclass ListView.
Override GetContainerForItemOverride() to return your ListViewItem override
This seems easier to me than subscribing to ItemContainer events and adding handlers dynamically.
This is what it looks like:
public class MouseUpListViewItem : ListViewItem
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {}
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) {}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
}
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
{
base.OnMouseRightButtonDown(e);
}
}
public class MouseUpListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new MouseUpListViewItem();
}
}
I like this technique because there is less code involved.
I'd like the main menu in my WPF app to behave like the main menu in IE8:
it's not visible when the app starts
pressing and releasing Alt makes it visible
pressing and releasing Alt again makes it invisible again
repeat until bored
How can I do this? Does it have to be code?
Added in response to answers submitted, because I'm still having trouble:
My Shell code-behind now looks like this:
public partial class Shell : Window
{
public static readonly DependencyProperty IsMainMenuVisibleProperty;
static Shell()
{
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.DefaultValue = false;
IsMainMenuVisibleProperty = DependencyProperty.Register(
"IsMainMenuVisible", typeof(bool), typeof(Shell), metadata);
}
public Shell()
{
InitializeComponent();
this.PreviewKeyUp += new KeyEventHandler(Shell_PreviewKeyUp);
}
void Shell_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.SystemKey == Key.LeftAlt || e.SystemKey == Key.RightAlt)
{
if (IsMainMenuVisible == true)
IsMainMenuVisible = false;
else
IsMainMenuVisible = true;
}
}
public bool IsMainMenuVisible
{
get { return (bool)GetValue(IsMainMenuVisibleProperty); }
set { SetValue(IsMainMenuVisibleProperty, value); }
}
}
You can use the PreviewKeyDown event on the window. To detect the Alt key you will need to check the SystemKey property of the KeyEventArgs, as opposed to the Key property which you normally use for most other keys.
You can use this event to set a bool value which has been declared as a DependencyProperty in the windows code behind.
The menu's Visibility property can then be bound to this property using the BooleanToVisibilityConverter.
<Menu
Visibility={Binding Path=IsMenuVisibile,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource BooleanToVisibilityConverter}}
/>
I just came across this problem myself. I tried hooking into the PreviewKeyDown event, but found it to be unreliable. Instead I found the InputManager class where you can hook into the EnterMenuMode from managed code. The manager exposes two events, for enter and exit. The trick is to not collapse the menu, but set it's container height to zero when it is to be hidden. To show it, simply clear the local value and it will take its previous height.
From my TopMenu user control:
public TopMenu()
{
InitializeComponent();
InputManager.Current.EnterMenuMode += OnEnterMenuMode;
InputManager.Current.LeaveMenuMode += OnLeaveMenuMode;
Height = 0;
}
private void OnLeaveMenuMode(object sender, System.EventArgs e)
{
Height = 0;
}
private void OnEnterMenuMode(object sender, System.EventArgs e)
{
ClearValue(HeightProperty);
}
I'd try looking into handling the PreviewKeyDown event on your window. I'm not sure if pressing Alt triggers this event or not, but if it does, then I'd toggle a bool which is bound to the visibility of the main menu of the window.
If PreviewKeyDown doesn't work, I'm not sure what else to try. You could look into getting at the actual Windows messages sent to your window, but that could get messy very quickly.
It would be better to use GetKeyboardState with VK_MENU to handle both left and right Alt, to mimic the behavior of IE / Windows Explorer (Vista+) you'll need to track the previously focused element to store focus, on a VK_MENU press whilst the focused element is within your main menu. You also want to be doing this work on PreviewKeyUp (not down).
See my answer to the following thread:
How to make WPF MenuBar visibile when ALT-key is pressed?
There I describe how to solve your problem with the class InputManager (from namespace System.Windows.Input).
You can register the classes events EnterMenuMode and LeaveMenuMode.