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.
Related
I decided to create my own dialog windows in WPF. I have it working using a Frame and navigating to a WPF page in the frame to get the appropriate dialog window I previously made. The problem comes when trying to return a value. For example if I have 'ok' and 'cancel' I would want to return a Boolean value upon pressing 'ok' or 'cancel' on the page displayed in the frame.
//This is what I'm using to display the dialog window frame.
public bool DisplayQuestion(string subject, string message)
{
AlertIsUp = true;
var questionPage = new QuestionPage(AlertFrame, subject, message);
AlertFrame.Navigate(questionPage);
if (MainFrame.Content != null && MainFrame.Content.ToString() == "System.Windows.Controls.WebBrowser")
{
MainFrame.Visibility = System.Windows.Visibility.Hidden;
}
//I need it to wait until a button on the dialog frame is pressed before continuing.
return QuestionResponse;
}
What happens is that is will immediately return the Boolean value which of course is always false. I need it to wait until 'ok' or 'cancel' are pressed within the page and then continue on to return it's value.
Here is the code within the page.
Frame AlertFrame { get; set; }
public bool AlertIsUp { get; set; }
public bool QuestionResponse { get; set; }
public QuestionPage(Frame alertFrame, string subject, string message)
{
InitializeComponent();
theMesssage.Content = message;
subjectLabel.Content = subject;
AlertFrame = alertFrame;
AlertIsUp = MainWindow.AlertIsUp;
QuestionResponse = MainWindow.QuestionResponse;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
AlertFrame.Content = null;
AlertIsUp = false;
QuestionResponse = false;
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
AlertFrame.Content = null;
AlertIsUp = false;
QuestionResponse = true;
}
Of course if I just add While(AlertIsUp) then if freezes the GUI.
It is very possible that I am doing things backward since I have not taken any formal training in C#. Thank you for your kind responses to my first post on this site.
I actually found a solution to this problem here:
http://www.codeproject.com/Articles/36516/WPF-Modal-Dialog
The solution ended up placing this short piece of code:
while (AlertIsActive)
{
if (this.Dispatcher.HasShutdownStarted ||
this.Dispatcher.HasShutdownFinished)
{
break;
}
this.Dispatcher.Invoke(
DispatcherPriority.Background,
new ThreadStart(delegate { }));
Thread.Sleep(20);
}
You can create a delegate in your dialog Window and attach a handler from the same method that creates and shows it. Then you can call the delegate when the Button is clicked and the launching class will get called. You'll then know the value and be able to close the Window.
If you don't know about delegates then you should definitely read the Delegates (C# Programming Guide) page on MSDN to help you understand this solution. You could do something like this:
In your dialog Window:
public void delegate Response(bool response);
public Response OnButtonClick { get; set; }
Then in the code that launches the dialog Window:
DialogWindow dialogWindow = new DialogWindow();
dialogWindow.OnButtonClick += OnButtonClick;
dialogWindow.Show();
...
public void OnButtonClick(bool response)
{
if (response) { /* Ok */ }
else { /* Cancel */ }
}
UPDATE >>>
Apologies for forgetting to show you the crucial part. When the Button is clicked, the dialog Window calls the delegate:
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
AlertFrame.Content = null;
AlertIsUp = false;
QuestionResponse = false;
if (OnButtonClick != null) OnButtonClick(QuestionResponse);
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
AlertFrame.Content = null;
AlertIsUp = false;
QuestionResponse = true;
if (OnButtonClick != null) OnButtonClick(QuestionResponse);
}
Of course, you won't really have much need for your QuestionResponse property and you could just as easily return true or false in the QuestionResponse delegate. Once that is called, the handler will get the response.
Regarding your comment about you not using different Windows, it makes little difference with delegates, it will work just the same. You can use them when there's no UI involved at all.
I have several comboboxes with the same properties.
Dropdownstyle : Dropdownlist
AutoCompleteMode: SuggestAppend
AutoCompleteSource: ListItems
For example, I have a dropdownlist cboxStates that has 50 states of United States of America in Items Collection entered manually. When I type in WI, it is highlighted among WA,WV,WI,WY but if I tab over/press enter/mouse click on another control, WA is selected instead of WI which is highlighted. This is total random and it happens to comboboxes that are binded dynamically. And also, they do not have any events.
This seems to be an issue that has been submitted to Connect. There's a workaround there which extends the default ComboBox control and fixes the issue. The extended ComboBox code is horribly formatted on the Connect site, so here's the nicer version :)
public class BetterComboBox : ComboBox
{
private int _windows7CorrectedSelectedIndex = -1;
private int? _selectedIndexWhenDroppedDown = null;
protected override void OnDropDown(EventArgs e)
{
_selectedIndexWhenDroppedDown = SelectedIndex;
base.OnDropDown(e);
}
private bool _onDropDownClosedProcessing = false;
protected override void OnDropDownClosed(EventArgs e)
{
if (_selectedIndexWhenDroppedDown != null && _selectedIndexWhenDroppedDown != SelectedIndex)
{
try
{
_onDropDownClosedProcessing = true;
OnSelectionChangeCommitted(e);
}
finally
{
_onDropDownClosedProcessing = false;
}
}
base.OnDropDownClosed(e);
if (SelectedIndex != _windows7CorrectedSelectedIndex)
{
SelectedIndex = _windows7CorrectedSelectedIndex;
OnSelectionChangeCommitted(e);
}
}
protected override void OnSelectionChangeCommitted(EventArgs e)
{
if (!_onDropDownClosedProcessing)
_windows7CorrectedSelectedIndex = SelectedIndex;
_selectedIndexWhenDroppedDown = null;
base.OnSelectionChangeCommitted(e);
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
bool alreadyMatched = true;
if (_windows7CorrectedSelectedIndex != SelectedIndex)
{
_windows7CorrectedSelectedIndex = SelectedIndex;
alreadyMatched = false;
}
base.OnSelectedIndexChanged(e);
//when not dropped down, the SelectionChangeCommitted event does not fire upon non-arrow keystrokes due (I suppose) to AutoComplete behavior
//this is not acceptable for my needs, and so I have come up with the best way to determine when to raise the event, without causing duplication of the event (alreadyMatched)
//and without causing the event to fire when programmatic changes cause SelectedIndexChanged to be raised (_processingKeyEventArgs implies user-caused)
if (!DroppedDown && !alreadyMatched && _processingKeyEventArgs)
OnSelectionChangeCommitted(e);
}
private bool _processingKeyEventArgs = false;
protected override bool ProcessKeyEventArgs(ref Message m)
{
try
{
_processingKeyEventArgs = true;
return base.ProcessKeyEventArgs(ref m);
}
finally
{
_processingKeyEventArgs = false;
}
}
}
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);
}
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.
The SelectedIndexChanged event gets fired in my application from a combo box when:
the user chooses a different
item in the combo box, or when:
my own code updates the combo
box's SelectedItem to reflect that
the combo box is now displaying
properties for a different object.
I am interested in the SelectedIndexChanged event for case 1, so that I can update the current object's properties. But in case 2, I do not want the event to fire, because the object's properties have not changed.
An example may help. Let's consider that I have a list box containing a list of people and I have a combo box representing the nationality of the currently selected person in the list. Case 1 could happen if Fred is currently selected in the list, and I use the combo box to change his nationality from English to Welsh. Case 2 could happen if I then select Bob, who is Scottish, in the list. Here, my list update event-handler code sees that Bob is now selected, and updates the combo box so that Scottish is now the selected item. This causes the combo box's SelectedIndexChanged event to be fired to set Bob's nationality to Scottish, even though it already is Scottish.
How can I update my combo box's SelectedItem property without causing the SelectedIndexChanged event to fire? One way would be to unregister the event handler, set SelectedItem, then re-register the event handler, but this seems tedious and error prone. There must be a better way.
I created a class I called SuspendLatch. Offers on a better name are welcome, but it does what you need and you would use it like this:
void Method()
{
using (suspendLatch.GetToken())
{
// Update selected index etc
}
}
void listbox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (suspendLatch.HasOutstandingTokens)
{
return;
}
// Do some work
}
It's not pretty, but it does work, and unlike unregistering events or boolean flags, it supports nested operations a bit like TransactionScope. You keep taking tokens from the latch and it's only when the last token is disposed that the HasOutstandingTokens returns false. Nice and safe. Not threadsafe, though...
Here's the code for SuspendLatch:
public class SuspendLatch
{
private IDictionary<Guid, SuspendLatchToken> tokens = new Dictionary<Guid, SuspendLatchToken>();
public SuspendLatchToken GetToken()
{
SuspendLatchToken token = new SuspendLatchToken(this);
tokens.Add(token.Key, token);
return token;
}
public bool HasOutstandingTokens
{
get { return tokens.Count > 0; }
}
public void CancelToken(SuspendLatchToken token)
{
tokens.Remove(token.Key);
}
public class SuspendLatchToken : IDisposable
{
private bool disposed = false;
private Guid key = Guid.NewGuid();
private SuspendLatch parent;
internal SuspendLatchToken(SuspendLatch parent)
{
this.parent = parent;
}
public Guid Key
{
get { return this.key; }
}
public override bool Equals(object obj)
{
SuspendLatchToken other = obj as SuspendLatchToken;
if (other != null)
{
return Key.Equals(other.Key);
}
else
{
return false;
}
}
public override int GetHashCode()
{
return Key.GetHashCode();
}
public override string ToString()
{
return Key.ToString();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources.
parent.CancelToken(this);
}
// There are no unmanaged resources to release, but
// if we add them, they need to be released here.
}
disposed = true;
// If it is available, make the call to the
// base class's Dispose(Boolean) method
//base.Dispose(disposing);
}
}
}
I think the best way would be to use a flag variable:
bool updatingCheckbox = false;
void updateCheckBox()
{
updatingCheckBox = true;
checkbox.Checked = true;
updatingCheckBox = false;
}
void checkbox_CheckedChanged( object sender, EventArgs e )
{
if (!updatingCheckBox)
PerformActions()
}
[Edit: Posting only the code is not really clear]
In this case, the event handler wouldn't perform its normal operations when the checkbox is changed through updateCheckBox().
I have always used a boolean flag variable to protect against unwanted event handlers. The TaskVision sample application taught me how to do this.
Your event handler code for all of your events will look like this:
private bool lockEvents;
protected void MyEventHandler(object sender, EventArgs e)
{
if (this.lockEvents)
{
return;
}
this.lockEvents = true;
//Handle your event...
this.lockEvents = false;
}
I let the event fire. But, I set a flag before changing the index and flip it back after. In the event handler, I check if the flag is set and exit the handler if it is.
I think your focus should be on the object and not on the event that's occuring.
Say for example you have the event
void combobox_Changed( object sender, EventArgs e )
{
PerformActions()
}
and PerformActions did something to the effect of
void PerformActions()
{
(listBox.SelectedItem as IPerson).Nationality =
(comboBox.SelectedItem as INationality)
}
then inside the Person you would expect to see something to the effect of
class Person: IPerson
{
INationality Nationality
{
get { return m_nationality; }
set
{
if (m_nationality <> value)
{
m_nationality = value;
this.IsDirty = true;
}
}
}
}
the point here is that you let the object keep track of what is happening to itself, not the UI. This also lets you keep track of dirty flag tracking on your objects, which could be useful for persistence later on.
This also keeps your UI clean and keeps it from getting odd event registration code that will most likely be error prone.
I have finally found a solution to avoid the uncessary event from being fired too many time.
I use a counter and I only hook/unhook the events I want to mask once when it is not needed, and when it is needed again.
The example below shows how I hide the CellValueChanged event of a datagrid.
EventMask valueChangedEventMask;
// In the class constructor
valueChangedEventMask = new EventMask(
() => { dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged); },
() => { dgv.CellValueChanged -= new DataGridViewCellEventHandler(dgv_CellValueChanged); }
);
// Use push to hide the event and pop to make it available again. The operation can be nested or be used in the event itself.
void changeCellOperation()
{
valueChangedEventMask.Push();
...
cell.Value = myNewCellValue
...
valueChangedEventMask.Pop();
}
// The class
public class EventMask
{
Action hook;
Action unHook;
int count = 0;
public EventMask(Action hook, Action unHook)
{
this.hook = hook;
this.unHook = unHook;
}
public void Push()
{
count++;
if (count == 1)
unHook();
}
public void Pop()
{
count--;
if (count == 0)
hook();
}
}