I have a Windows Forms application with some buttons for the F keys. When you place the mouse over the buttons the get grey, and when you click they get a slightly lighyer grey. I would like to mimic that behaviour with F key keystrokes... how would you do it?
Set the Form's KeyPreview property to true, handle the KeyDown and KeyUp events, track which function key(s) are pressed, and call the Invalidate method on the button for each key the went down or up.
Then, handle the button's Paint event, and, if its key is down, use the ButtonRenderer class to draw the button as if it were pressed.
Use Button.PerformClick().
Finally I implemented the button changing the background:
class FunctionButton : Button
{
private Color m_colorOver;
private bool m_isPressed;
public FunctionButton() : base()
{
m_isPressed = false;
}
protected override void OnGotFocus(EventArgs e)
{
OnMouseEnter(null);
base.OnGotFocus(e);
}
protected override void OnLostFocus(EventArgs e)
{
if (!m_isPressed)
{
OnMouseLeave(null);
}
base.OnLostFocus(e);
}
protected override void OnMouseLeave(EventArgs e)
{
if (!Focused && !m_isPressed)
{
base.OnMouseLeave(e);
}
}
public void FunctionKeyPressed()
{
// Handle just the first event
if (!m_isPressed)
{
m_isPressed = true;
m_colorOver = FlatAppearance.MouseOverBackColor;
FlatAppearance.MouseOverBackColor = FlatAppearance.MouseDownBackColor;
OnMouseEnter(null);
PerformClick();
}
}
public void FunctionKeyReleased()
{
m_isPressed = false;
FlatAppearance.MouseOverBackColor = m_colorOver;
if (Focused)
{
OnMouseEnter(null);
}
else
{
base.OnMouseLeave(null);
}
}
}
It is not the most clean way but it works fine. I would like more examples doing this with a cleaner and more elegant style.
SetCapture and ReleaseCapture might work.
Related
My question is regarding the PanGestureRecognizer on Xamarin.Forms, specifically on UWP. The pan gesture works great and the menu slides in and out but if you move the mouse cursor outside of the application window it does not trigger GestureStatus.Canceled, or GestureStatus.Completed. I've simplified the code below because it is not important. The pan gesture is working correctly and the menu is sliding in and out with the pan. The issue is it is not triggering anything if you move the cursor outside of the app window.
Code below:
panContainer is a simple ContentView.
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += PanUpdated;
panContainer.GestureRecognizers.Add(panGesture);
private void PanUpdated(object sender,PanUpdatedEventArgs e) {
switch(e.StatusType) {
case GestureStatus.Started:
break;
case GestureStatus.Running:
menuLayout.TranslationX = (float)e.TotalX;
break;
case GestureStatus.Completed:
case GestureStatus.Canceled: //I assumed this would be called!
if(menuLayout.TranslationX < 100) {
Close();
} else {
Open();
}
break;
}
}
Thanks in advance!
if you move the mouse cursor outside of the application window it does not trigger GestureStatus.Canceled, or GestureStatus.Completed. I've simplified the code below because it is not important. The pan gesture is working correctly and the menu is sliding in and out with the pan. The issue is it is not triggering anything if you move the cursor outside of the app window.
I have tested your code and reproduce the issue. I tried to find the cause of the problem in the source code. I found the following code.
void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
uint id = e.Pointer.PointerId;
if (_fingers.Contains(id))
_fingers.Remove(id);
PinchComplete(false);
PanComplete(false);
}
void OnPointerExited(object sender, PointerRoutedEventArgs e)
{
uint id = e.Pointer.PointerId;
if (_fingers.Contains(id))
_fingers.Remove(id);
PinchComplete(true);
PanComplete(true);
}
void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
uint id = e.Pointer.PointerId;
if (!_fingers.Contains(id))
_fingers.Add(id);
}
void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
uint id = e.Pointer.PointerId;
if (_fingers.Contains(id))
_fingers.Remove(id);
PinchComplete(true);
PanComplete(true);
}
The PanComplete method will be invoked when you PointerExited,ManipulationCompleted etc. But it has not been invoked when the pointer exited. And then I tried to test PointerExited and ManipulationCompleted event in uwp native project. Both are working properly.
<Border Width="200" Height="200" Background="Red" PointerEntered="Border_PointerEntered" PointerExited="Border_PointerExited" ManipulationCompleted="Border_ManipulationCompleted"/>
So,there may be some issues with xamarin.
Workable solution based on the previous comment
//Shared part
public class PanOutsideWindowEffect : RoutingEffect
{
public event EventHandler<PanUpdatedEventArgs> PanUpdated;
public PanOutsideWindowEffect() : base($"{ResolutionGroupName.Organization}.{nameof(PanOutsideWindowEffect)}")
{
}
public void SendPanCancel()
{
PanUpdated?.Invoke(this, new PanUpdatedEventArgs(GestureStatus.Canceled, 0));
}
}
//UWP part
public class PanOutsideWindowEffect : PlatformEffect
{
private FrameworkElement frameworkElement;
private MobileApp.Effects.PanOutsideWindowEffect effect;
protected override void OnAttached()
{
frameworkElement = Control == null ? Container : Control;
effect = (MobileApp.Effects.PanOutsideWindowEffect)Element.Effects.First(e => e is MobileApp.Effects.PanOutsideWindowEffect);
if (frameworkElement != null)
{
frameworkElement.PointerExited += OnPointerExited;
}
}
protected override void OnDetached()
{
if (frameworkElement != null)
{
frameworkElement.PointerExited -= OnPointerExited;
}
}
private void OnPointerExited(object sender, PointerRoutedEventArgs args)
{
effect?.SendPanCancel();
}
}
I have a textbox where its Leave event is like this:
private async void TxtLotTextLeave(object sender, EventArgs e)
{
if (!isChecked)
{
isChecked = true;
var mylength = BarcodeUtil.LotStripZeroes(txtLot.Text.Trim()).Length;
var strippedLot = BarcodeUtil.LotStripZeroes(txtLot.Text.Trim());
if (mylength > 0)
{
if (mylength.Between(16, 18) &&
(strippedLot.StartsWith(AppState.LotOldStandardDigits) ||
strippedLot.StartsWith(AppState.LotStandardDigits)))
{
await GetLotData();
}
else
{
ShowAppMessage(AppMessages["WrongLot"], 0, Color.Black, Color.BlanchedAlmond);
txtLot.Text = "";
LotFocus(true);
}
}
}
}
99% of the time i need this event to work like this.
BUT i only need when a specific button is clicking NOT to fire it.
Button click:
private void BtnClearClick(object sender, EventArgs e)
{
ClearForm();
LotFocus(true);
}
I tried the obvious to use a global bool variable and set it to false in click event and check it in leave but it doesnt work..I suspect that has to do with async?
Additional Info:
What i tried is to create a bool variable needTxtValidation and try to set it to false in various places like button click, textbox keypress, button mousedown, but it didnt work.
Alright, here's the dirty way I managed to find. You need to inherit the Button, override the WndProc and expose a boolean which says whether currently processing MouseDown.
class ButtonEx : Button
{
public bool IsInMouseDown { get; set; }
protected override void WndProc(ref Message m)
{
const int WM_LBUTTONDOWN = 0x0201;
try
{
if (m.Msg == WM_LBUTTONDOWN)
IsInMouseDown = true;
base.WndProc(ref m);
}
finally //Make sure we set the flag to false whatever happens.
{
if (m.Msg == WM_LBUTTONDOWN)//Required to fight with reentracy
IsInMouseDown = false;
}
}
}
Then in your leave method
private async void TxtLotTextLeave(object sender, EventArgs e)
{
if (yourButton.IsInMouseDown)
{
Console.WriteLine("Ignoring Leave");
return;
}
...
}
This works, however I won't guarantee it will continue to work always. You may need to address some corner cases or obvious thing which I've missed. That's a very hacky code, you are better off re-designing the logic.
I have an application with a MenuStrip and every time I hover my mouse over a MenuItem, it highlights blue.
I have tried to change the BackColor and ForeColor but that wasn't the problem.
Is there a way to disable this?
This would be incredibly un-useful to the end user:
internal class NoHighlightRenderer : ToolStripProfessionalRenderer {
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) {
if (e.Item.OwnerItem == null) {
base.OnRenderMenuItemBackground(e);
}
}
}
Then apply it to your MenuStrip:
menuStrip1.Renderer = new NoHighlightRenderer();
I have many controls in a window. Requirement is to know which control gets the focus from the lost focus event of a control.
Say, A Text box and it has the focus. Now I am clicking a button. while doing this, need to know that i am moving the focus to button from the Text box lost focus event.
So how could i achieve this..
This is what I did and its working for me
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
lostFocusControl = e.OldFocus;
}
private void PauseBttn_PreviewKeyDown(object sender, KeyEventArgs e)
{
/**invoke OnPreviewLostKeyboardFocus handller**/
}
Hope it will help
You can use FocusManager to handle this,
In your LostFocusEvent, Use FocusManager.GetFocusedElement()
uiElement.LostFocus+=(o,e)=>
{
var foo=FocusManager.GetFocusedElement();
}
The following class watches the FocusManager for changes in focus, it's a looped thread so you have to put up with the fact that it's running but when focus changes it will just raise an event letting you know what changed.
Just add these two classes to your project.
public class FocusNotifierEventArgs : EventArgs
{
public object OldObject { get; set; }
public object NewObject { get; set; }
}
public class FocusNotifier : IDisposable
{
public event EventHandler<FocusNotifierEventArgs> OnFocusChanged;
bool isDisposed;
Thread focusWatcher;
Dispatcher dispatcher;
DependencyObject inputScope;
int tickInterval;
public FocusNotifier(DependencyObject inputScope, int tickInterval = 10)
{
this.dispatcher = inputScope.Dispatcher;
this.inputScope = inputScope;
this.tickInterval = tickInterval;
focusWatcher = new Thread(new ThreadStart(FocusWatcherLoop))
{
Priority = ThreadPriority.BelowNormal,
Name = "FocusWatcher"
};
focusWatcher.Start();
}
IInputElement getCurrentFocus()
{
IInputElement results = null;
Monitor.Enter(focusWatcher);
dispatcher.BeginInvoke(new Action(() =>
{
Monitor.Enter(focusWatcher);
results = FocusManager.GetFocusedElement(inputScope);
Monitor.Pulse(focusWatcher);
Monitor.Exit(focusWatcher);
}));
Monitor.Wait(focusWatcher);
Monitor.Exit(focusWatcher);
return results;
}
void FocusWatcherLoop()
{
object oldObject = null;
while (!isDisposed)
{
var currentFocus = getCurrentFocus();
if (currentFocus != null)
{
if (OnFocusChanged != null)
dispatcher.BeginInvoke(OnFocusChanged, new object[]{ this, new FocusNotifierEventArgs()
{
OldObject = oldObject,
NewObject = currentFocus
}});
oldObject = currentFocus;
}
}
Thread.Sleep(tickInterval);
}
}
public void Dispose()
{
if (!isDisposed)
{
isDisposed = true;
}
}
}
Then in your code behind, create a new instance of the Focus Notifier class and hook on to it's OnFocusChanged event, remember to dispose it at the end or the thread will keep your app open.
public partial class MainWindow : Window
{
FocusNotifier focusNotifier;
public MainWindow()
{
InitializeComponent();
focusNotifier = new FocusNotifier(this);
focusNotifier.OnFocusChanged += focusNotifier_OnFocusChanged;
}
void focusNotifier_OnFocusChanged(object sender, FocusNotifierEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.OldObject);
System.Diagnostics.Debug.WriteLine(e.NewObject);
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
focusNotifier.Dispose();
base.OnClosing(e);
}
}
have you tried to register your controls to Control.LostFocus event and there you can check for Form.ActiveControl, to determine which control currently has the focus
The functionality I'm after is a cross between a check list box and a list box in multi selection mode.
For list box items A and B
A then B results in a single selection that moves from A to B.
A then control-click B results in a multi-selection of A and B.
(What I want is):
A then A results in an A toggling on and off.
I thought this would be easy but I can't figure it out. Maybe I'm missing something obvious or maybe I'm thinking the wrong way and nobody really wants a listbox whos items toggle on/off.
If you set SelectionMode to MultiSimple this gets you the control-click multi-selection and the toggling on and off.
To get the moving selection to work you could handle the SelectedIndexChanged event and add some logic to de-select the other items if control isn't pressed. If I have more time later I could try to create some basic code for it, but this should be somewhere to start.
You already have the behavior you want if you set the ListBox.SelectionMode to MultiExtended and hold down control when making a selection.
If I understood correctly your problem, you want a ListBox with SelectionMode.One that can toggle selections with CTLR modifier similarly to SelectionMode.MultiSimple and SelectionMode.MultiExtented. Below my answer, just set ToggleSingleSelection to true. Also as a bonus it provides items click events that fires also when clicking already selected items.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Common
{
public class ListBoxEx : ListBox
{
public event ItemClickedEventHandler ItemClick;
public event ItemClickedEventHandler ItemDoubleClick;
/// <summary>
/// Toggle selections when list has SelectionMode.One
/// </summary>
public bool ToggleSingleSelection { get; set; }
int _PreSelectedIndex = -1;
int _PrevClickedItem = -1;
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
}
protected override void WndProc(ref Message m)
{
const int WM_LBUTTONDOWN = 0x201;
switch (m.Msg)
{
case WM_LBUTTONDOWN:
// NOTE: Unfortunately SelectedIndex is already setted before OnMouseDown,
// so we must intercept mouse click even before to compare
_PreSelectedIndex = SelectedIndex;
break;
}
base.WndProc(ref m);
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
// Reset clicked event, also for double click
_PrevClickedItem = -1;
int selectedIndex = SelectedIndex;
if (selectedIndex != -1)
{
Rectangle selectedItemRectangle = GetItemRectangle(selectedIndex);
if (selectedItemRectangle.Contains(e.Location))
{
_PrevClickedItem = selectedIndex;
// Check when to toggle selection
if (SelectionMode == SelectionMode.One && ToggleSingleSelection && ModifierKeys.HasFlag(Keys.Control)
&& _PreSelectedIndex != -1 && selectedIndex == _PreSelectedIndex)
{
SelectedIndex = -1;
}
if (_PrevClickedItem != -1)
OnItemClick(new ItemClickedEventArgs() { ItemIndex = _PrevClickedItem });
}
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e)
{
base.OnMouseDoubleClick(e);
if (_PrevClickedItem != -1)
OnItemDoubleClick(new ItemClickedEventArgs() { ItemIndex = _PrevClickedItem });
}
protected virtual void OnItemDoubleClick(ItemClickedEventArgs args)
{
ItemDoubleClick?.Invoke(this, args);
}
protected virtual void OnItemClick(ItemClickedEventArgs args)
{
ItemClick?.Invoke(this, args);
}
}
public class ItemClickedEventArgs : EventArgs
{
public int ItemIndex { get; set; }
}
public delegate void ItemClickedEventHandler(Control sender, ItemClickedEventArgs args);
}