Under certain conditions I'd like to cancel the ValueChanged event of a NumericUpDown control in Winforms (e.g. when the value set in the control is too high in relation to another value in a TextBox).
However, the EventArgs passed as argument into the event handler for the NumericUpDown control doesn't offer anything like "Cancel", so how can I do it?
private void nudMyControl_ValueChanged(object sender, EventArgs e)
{
// Do some stuff, but only if the value is within a certain range.
// Otherwise, cancel without doing anything.
}
You probably can handle this situation by using Minimum & maximum Properties, yet there are some solutions.
One way to do it is by creating a custom control, although it is nasty in my idea.
public partial class CustomNumericUpDown : NumericUpDown
{
public CustomNumericUpDown()
{
InitializeComponent();
}
protected override void OnTextBoxKeyDown(object source, KeyEventArgs e)
{
if (MyCustomCondition())
{
e.Handled = true;
}
base.OnTextBoxKeyDown(source, e);
}
private bool MyCustomCondition()
{
var checkOut = false;
//if (something == foo)
//{
checkOut = true;
//}
return checkOut;
}
}
You also can do stuff for ValueChanged:
This is some dummy sample, yet you can change it in your way:
public virtual decimal CurrentEditValue { get; internal set; } = 0M;
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
if (decimal.TryParse(Text, out decimal v))
{
CurrentEditValue = v;
OnValueChanged(e);
}
}
protected override void OnValueChanged(EventArgs e)
{
// if(CurrentEditValue == foo) Do_stuff.
base.OnValueChanged(e);
}
Related
I have a C# WinForm application where I want to implement a NumericUpDown component that increments / decrements its value depending whether or not the control key is pressed on clicking to the arrow button. If the control key is pressed, it should increment by 1, by 0.1 otherwise. Using the ValueChanged event does not work, because the value has already changed. I also tried to use the Click and MouseClick event but they are risen after the ValueChanged Event.
Has anyone an Idea how I can achieve this?
// This method added to either Click or MouseClick event
private void Click_Event(object sender, EventArgs e)
{
if (Control.ModifierKeys == Keys.Control)
{
numSetPoint1.Increment = 1; // Setting the Increment value takes effect on the next click
}
else
{
numSetPoint1.Increment = 0.1m;
}
}
// Event added to ValueChanged
private void ValueChanged_Event(object sender, EventArgs e)
{
// the same here
}
One way to do this is by making a CustomNumericUpDown control and swapping out all instances of the regular NumericUpDown control in your designer file.
Then you can just override the methods that implement the value up-down at the source and call the base class or not depending on the static value of Control.ModifierKeys property.
class CustomNumericUpDown : NumericUpDown
{
public CustomNumericUpDown() => DecimalPlaces = 1;
public override void UpButton()
{
if(ModifierKeys.Equals(Keys.Control))
{
Value += 0.1m;
}
else
{
// Call Default
base.UpButton();
}
}
public override void DownButton()
{
if (ModifierKeys.Equals(Keys.Control))
{
Value -= 0.1m;
}
else
{
base.DownButton();
}
}
}
You can implement it at form level.
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/input-keyboard/how-to-handle-forms?view=netdesktop-6.0
Handle the KeyPress or KeyDown event of the startup form, and set the
KeyPreview property of the form to true.
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.ControlKey)
{
numSetPoint1.Increment = 1;
}
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.ControlKey)
{
numSetPoint1.Increment = 0.1m;
}
}
I want to mark a DataGridViewCellEventArgs as handled so that nothing downstream from it interferes with the way it was handled.
The DataGridViewCellEventArgs class does not have a handled property, and neither does its base class.
The event that I am working with isCellMouseEnter
This is the base DataGridView control that I am instantiating from:
public class DataGridViewWithFormatting : System.Windows.Forms.DataGridView
{
protected override void OnCellMouseEnter(DataGridViewCellEventArgs e)
{
base.OnCellMouseEnter(e);
this.Cursor = Cursors.Default;
}
}
This is the DataGridView control that I am using in my form:
private CustomControls.DataGridViewWithFormatting dgvItems;
and...
dgvItems.CellMouseEnter += new EventHandler(dgvItems_CellMouseEnter);
then...
private void dgvItems_CellMouseEnter()
{
this.Cursor = Cursors.Hand;
}
You can use either of the following solutions depending on your requirements.
1 - Use BeginInvoke
To set the cursor in CellMouseEnter, you can use BeginInvoke:
private void Dgv_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
{
BeginInvoke(new Action(() => dgv.Cursor = Cursors.Hand));
}
2 - Create a new cancellable(handle-able) DataGridViewCellEventArgs
Create a new MyDataGridViewCellEventArgs event args deriving from DataGridViewCellEventArgs having a Handled property. Then in your derived DataGridView, when calling base.OnCellMouseEnter, pass an instance of the new cancellable(handle-able) event args. In the event handlers, still keep the DataGridViewCellEventArgs in the signature. To cancel, cast it to MyDataGridViewCellEventArgs and cancel it by setting Handled = true;:
public class DataGridViewWithFormatting : System.Windows.Forms.DataGridView
{
protected override void OnCellMouseEnter(DataGridViewCellEventArgs e)
{
var myE = new MyDataGridViewCellEventArgs(e);
base.OnCellMouseEnter(myE);
if (!myE.Handled)
this.Cursor = Cursors.Default;
}
}
public class MyDataGridViewCellEventArgs : DataGridViewCellEventArgs
{
public bool Handled { get; set; } = false;
public MyDataGridViewCellEventArgs(DataGridViewCellEventArgs e)
: base(e.ColumnIndex, e.RowIndex) { }
}
And the event handler:
private void Dgv_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
{
dgv.Cursor = Cursors.Hand;
var myE = e as MyDataGridViewCellEventArgs;
if (myE != null)
myE.Handled = true;
}
I have having some trouble with communication from a usercontrol to the main page. The order in which events are raised means that the action on the user control occurs too late in the post back to have an effect on the main page.
For example, I have a button on a user control which, when pressed, raises a custom event that is being listened for on the main page. When the button is pressed the postback order is:
page_load - on the main page
page_load - on the usercontrol (the user control is loaded programitically by the main page page_load)
The button call back on the user control
The event call back method on the main page
By this point, it seems it is too late for anything the event call back method does to have any effect on the rendered page, for example I am trying to use it to change the usercontrol that is being displayed.
What other techniques can be used for this kind of communication?
Relevant code
Main page:
public string LastLoadedControl
{
get
{
return Session["LastLoaded"] as string;
}
set
{
Session["LastLoaded"] = value;
}
}
private void LoadUserControl()
{
string controlPath = LastLoadedControl;
ContentPlaceholder.Controls.Clear();
if (string.IsNullOrEmpty(controlPath))
controlPath = Utils.Paths.USERCTRL_BASE + "Main.ascx";
Control uc = Page.LoadControl(controlPath);
ContentPlaceholder.Controls.Add(uc);
}
protected void Page_Load(object sender, EventArgs e)
{
LoadUserControl();
if (!IsPostBack)
Utils.Events.redirectPage += Events_redirectPage;
}
private void Events_redirectPage(string path)
{
if (path.Equals("Main"))
{
//Session.Clear();
//Session.Abandon();
}
else LastLoadedControl = Paths.USERCTRL_BASE + path + ".ascx"
LoadUserControl();
}
User control
protected void profileBtn_Click(object sender, EventArgs e)
{
Utils.Events.triggerRedirectPage("Login");
}
Event
public class Events
{
public delegate void redirectEvent(string path);
public static event redirectEvent redirectPage;
public static void triggerRedirectPage(String path)
{
if (Utils.Events.redirectPage != null)
Utils.Events.redirectPage(path);
}
}
There are two approaches that you can follow.
Approach 1:
public interface IEventProvider
{
void TriggerEvent();
}
public class YourPage: Page, IEventProvider
{
// Other page methods
public void TriggerEvent()
{
// Your Implementation
}
}
public class YourUserControl : WebUserControl
{
protected void profileBtn_Click(object sender, EventArgs e)
{
IEventProvider eventProvider = this.Page as IEventProvider;
if(eventProvider != null)
eventProvider.TriggerEvent();
}
}
Approach 2:
public interface IEventProvider
{
// This does not have to be a boolean. You can use a string / enum / anything that suits your implementation
bool Trigger {get; set;}
}
public class YourPage: Page, IEventProvider
{
// Other page methods
protected override void OnLoadComplete(EventArgs e)
{
// This will be raised when all the events have fired for all the controls in the page.
if(this.Trigger)
TriggerEvent();
}
protected void TriggerEvent()
{
// Your code here
}
public bool Trigger
{
get;
set;
}
}
public class YourUserControl : WebUserControl
{
protected void profileBtn_Click(object sender, EventArgs e)
{
IEventProvider eventProvider = this.Page as IEventProvider;
if(eventProvider != null)
eventProvider.Trigger = true;
}
}
I use OwnerDrawAll properties for my control:
this.customTreeView.DrawMode =System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll;
this.customTreeView.DrawNode +=
new System.Windows.Forms.DrawTreeNodeEventHandler(customTreeView_DrawNode);
private void customTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e) {
if(!myComboBoxChanged) { // Draw the whole control(tree and info)
drawNode(e);
drawInfo(e);
} else { // Draw only info
drawInfo(e);
}
}
Then I use text changed event:
private void cBox_TextChanged(object sender, EventArgs e)
{
text = cBox.Text; // I need this in drawInfo()
myComboBoxChanged = true;
this.customTreeView.Invalidate(); // It doesn't do what I want
myComboBoxChanged = false;
}
Here Invalidate() method redrawing the whole tree, how can I fix this so only drawInfo() will be called ?
the better way is to create your own TreeView Class and encapsulate all of your methods and properties so :
public class MyTreeView : TreeView
{
public bool TextBoxChanged { get; set; }
public MyTreeView()
{
DrawMode = TreeViewDrawMode.OwnerDrawAll;
DrawNode += new System.Windows.Forms.DrawTreeNodeEventHandler(customTreeView_DrawNode);
}
protected override void OnInvalidated(InvalidateEventArgs e)
{
//comment the below line to create your own Invalidate
//base.OnInvalidated(e);
}
private void customTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
if (!TextBoxChanged)
{ // Draw the whole control(tree and info)
drawNode(e);
drawInfo(e);
}
else
{ // Draw only info
drawInfo(e);
}
}
private void drawNode(DrawTreeNodeEventArgs e)
{
//...........
}
private void drawInfo(DrawTreeNodeEventArgs e)
{
//...........
}
}
I have a button. On Button.MouseRightButtonDown, I'm calling Mouse.Capture(button), because I want to detect if someone releases right-click outside of the Button.
I also have a Button.MouseLeave event registered. If someone right-click-drags the mouse off the button, I want this event to trigger.
Unfortunately, it seems like Mouse.Capture somehow blocks MouseLeave from occuring.
Does anyone know a workaround, or maybe can point out where I'm going wrong?
(By the way, if anyone's curious what I'm doing this for, see my other question.)
When mouse is captured, you can use MouseMove and hit-testing to determine whether the mouse within your element or another.
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (this.IsMouseCaptured)
{
HitTestResult ht = VisualTreeHelper.HitTest(this, e.GetPosition(this));
if (ht != null)
{
DependencyObject current = ht.VisualHit;
while (current != this && current != null)
{
current = VisualTreeHelper.GetParent(current);
}
if (current == this)
{
Debug.WriteLine("Inside");
return;
}
}
Debug.WriteLine("Outside");
}
}
The following code can be used to avoid tree walk:
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (this.IsMouseCaptured)
{
bool isInside = false;
VisualTreeHelper.HitTest(
this,
d =>
{
if (d == this)
{
isInside = true;
}
return HitTestFilterBehavior.Stop;
},
ht => HitTestResultBehavior.Stop,
new PointHitTestParameters(e.GetPosition(this)));
if (isInside)
{
Debug.WriteLine("Inside");
}
else
{
Debug.WriteLine("Outside");
}
}
}
Following on from my answer to your related question, if you use CaptureMouse() from UIElement in place of Mouse.Capture(...) then you will see the correct behaviour.
public class MyButton : Button
{
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
{
base.OnMouseRightButtonDown(e);
CaptureMouse();
}
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
{
base.OnMouseRightButtonUp(e);
ReleaseMouseCapture();
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
Debug.WriteLine("Mouse enter");
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
Debug.WriteLine("Mouse leave");
}
}