I running into an infinite loop problem.
I have two numeric up/down controls (Height and Width input parameters). When the user changes the value of one of the controls, I need to scale the other to keep a height to width ratio constant.
Is there a way to set the value of a control without invoking a ValueChanged Event. I only want the ValueChanged event to execute when the user changes the value.
private void FloorLength_ValueChanged(object sender, EventArgs e)
{
if (this.mCurrentDocument.System.SuperTrakSystem.FloorBitmap != null)
{
FloorWidth.Value = FloorLength.Value *
((decimal)this.mCurrentDocument.System.SuperTrakSystem.FloorBitmap.Height /
(decimal)this.mCurrentDocument.System.SuperTrakSystem.FloorBitmap.Width);
}
}
private void FloorWidth_ValueChanged(object sender, EventArgs e)
{
if (this.mCurrentDocument.System.SuperTrakSystem.FloorBitmap != null)
{
FloorLength.Value = FloorWidth.Value *
((decimal)this.mCurrentDocument.System.SuperTrakSystem.FloorBitmap.Width /
(decimal)this.mCurrentDocument.System.SuperTrakSystem.FloorBitmap.Height);
}
}
Thanks for your answers.
I came up with an alternate solution that works. User changing the value from the UI triggers the event, while programmatic Value parameter changes do not trigger the event.
using System;
using System.Windows.Forms;
namespace myNameSpace.Forms.UserControls
{
public class NumericUpDownSafe : NumericUpDown
{
EventHandler eventHandler = null;
public event EventHandler ValueChanged
{
add
{
eventHandler += value;
base.ValueChanged += value;
}
remove
{
eventHandler -= value;
base.ValueChanged -= value;
}
}
public decimal Value
{
get
{
return base.Value;
}
set
{
base.ValueChanged -= eventHandler;
base.Value = value;
base.ValueChanged += eventHandler;
}
}
}
}
I'm not that familiar with the NumericUpDown control, but there may not be a way to set the value without triggering the ValueChanged event. Instead, before you set the value, you could set a flag indicating that the event should be ignored, and clear the flag after setting the value. In your event handler, do nothing if the flag is set.
private bool ignoreEvent = false;
private void setValue(int value)
{
ignoreEvent = true;
FloorLength.Value = value;
ignoreEvent = false;
}
private void FloorLength_ValueChanged(object sender, EventArgs e)
{
if(ignoreEvent) { return; }
// your code here
}
In theory, these values should stabilize... Meaning if the user changes 1, the system changes the other and then the first one remains the same. Therefore, I would just add a check into both of the event handlers (pseudocode):
newValue = equation;
if(controlValue != newValue)
{
controlValue = newValue; //raises the event only when necessary.
}
Related
I have one UserControl in which I am using a Canvas, and in that Canvas one Rectangle. I want to create a click event for that user control (Canvas and Rectangle) which I then want to use in the main window.
The question is: I want to create a new click event for the UserControl. How to do it? Kindly show little example or the code.
A brief example on how to expose an event from the UserControl that the main window can register:
In your UserControl:
1 . Add the following declaration:
public event EventHandler UserControlClicked;
2 . In your UserControl_Clicked event raise the event like this:
private void UserControl_MouseDown(object sender, MouseButtonEventArgs e)
{
if (UserControlClicked != null)
{
UserControlClicked(this, EventArgs.Empty);
}
}
In your MainWindow:
Your usercontrol will now have a UserControlClicked event which you can register to:
<local:UserControl1 x:Name="UC" UserControlClicked="UC_OnUserControlClicked" />
i find this easier for passing value to handler:
public event Action<string> onUserCodeFetched;
private void btnEnterClicked(object sender, RoutedEventArgs e)
{
onUserCodeFetched(PersonellCode.Text);
PersonellCode.Text = "";
}
It's an old question, but I also found useful to bypass the event using a property-like custom event as in here. You can use a general EventHandler or you can be more specific (RoutedEventHandler, MouseButtonEventHandler, etc) to avoid later casts. For instance, to bypass a click event, you could write in your custom control class:
public readonly object objectLock = new object();
public event RoutedEventHandler CustomClick
{
add{ lock (objectLock) { myClickableInnerControl.Click += value;}}
remove {lock (objectLock) { myClickableInnerControl.Click -= value; }}
}
and for a PreviewMouseDown event, it would be something like:
public readonly object objectLock = new object();
public event MouseButtonEventHandler CustomPreviewMouseDown
{
add{ lock (objectLock) { myInnerControl.PreviewMouseDown += value;}}
remove {lock (objectLock) { myInnerControl.PreviewMouseDown -= value; }}
}
Where myInnerControl would be your canvas in this case.
Then you can initialize the event from xaml as
<local:myClickableControl x:Name="ClickCtrl1" CustomClick="ClickCtrl1_CustomClick" />
or
<local:myCustomControl x:Name="CustomCtrl1" CustomPreviewMouseDown="CustomCtrl1_CustomPreviewMouseDown" />
And from code behind:
ClickCtrl1.CustomClick+=ClickCtrl1_CustomClick;
or
CustomCtrl1.CustomPreviewMouseDown+=CustomCtrl1_CustomPreviewMouseDown;
You can also subscribe your callback to several inner controls events (being careful when they overlap as some events like previewMouseDown are not only fired by the front control but also by the controls underneath).
public readonly object objectLock = new object();
public event MouseButtonEventHandler CustomPreviewMouseDown
{
add{ lock (objectLock)
{
myInnerControl1.PreviewMouseDown += value;
myInnerControl2.PreviewMouseDown += value;
}}
remove {lock (objectLock)
{
myInnerControl1.PreviewMouseDown -= value;
myInnerControl2.PreviewMouseDown -= value;
}}
}
(If the inner controls partially overlap, you can use the corresponding eventArgs Handled property in your callback method to avoid repetitions)
Finally, you can add a layer of control to the event fired by your inner control:
bool MousePreviewDownUsed = false;
event MouseButtonEventHandler _myPreviewMouseDownEvent = null;
public event MouseButtonEventHandler CustomPreviewMouseDown
{
add
{
lock (objectLock)
{
_myPreviewMouseDownEvent += value;
if (value != null && !MousePreviewDownUsed)
{
myInnerControl.PreviewMouseDown += myInnerControl_PreviewMouseDown;
MousePreviewDownUsed = true;
}
}
}
remove
{
lock (objectLock)
{
if (_myPreviewMouseDownEvent != null)
{
_myPreviewMouseDownEvent -= value;
if ((_myPreviewMouseDownEvent == null ||
_myPreviewMouseDownEvent.GetInvocationList().Length == 0)
&& MousePreviewDownUsed)
{
_myPreviewMouseDownEvent = null;
myInnerControl.PreviewMouseDown -= myInnerControl_PreviewMouseDown;
MousePreviewDownUsed = false;
}
}
}
}
}
private void myInnerControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Do some previous operations or control whether the event must be broadcasted
_myPreviewMouseDownEvent?.Invoke(sender, e);
}
This event is initialized from xaml or code behind the same way as before.
What I am attempting to do is create a complex control that has a picture box, track slider and numeric up down controls. In the parent form, when the user clicks on an image, then this composite control appears and the background color is then sent to it and the image in the control is then set with that background color. Then if the user clicks on the image on the composite control, the parent form is then notified of the click event and then subsequently removes that specific composite control from the parent form.
Composite Control code
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace ctlClusterControlLib
{
public partial class UserControl1 : UserControl
{
private Color colImageBackground;
private int intThreadCount;
private PictureBox pictureBoxControl; // Compiler informs me that this is never assigned to and will always have its default value null.
private TrackBar trackBar; // Compiler informs me that this is never assigned to and will always have its default value null.
private NumericUpDown numericUpDown; // Compiler informs me that this is never assigned to and will always have its default value null.
private string strImageToolTip1;
private string strImageToolTip2;
private static object EventSubmitKey = new object();
public UserControl1()
{
InitializeComponent();
}
public Color ImageBackground
{
get { return colImageBackground; }
set { colImageBackground = value; Invalidate(); }
}
public int ThreadCount
{
get { return intThreadCount; }
set { intThreadCount = value; }
}
[
Category("Action"),
Description("Raised when the user clicks on the image.")
]
public event EventHandler PictureClick
{
add { Events.AddHandler(EventSubmitKey, value); }
remove { Events.RemoveHandler(EventSubmitKey, value); }
}
public event EventHandler TrackBarScroll
{
add { trackBar.Scroll += value; }
remove { trackBar.Scroll -= value; }
}
public event EventHandler numericUpDownChange
{
add { numericUpDown.ValueChanged += value; }
remove { numericUpDown.ValueChanged -= value; }
}
public string ImageToolTip1
{
get { return strImageToolTip1; }
set { strImageToolTip1 = value; }
}
public string ImageToolTip2
{
get { return strImageToolTip2; }
set { strImageToolTip2 = value; }
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
numericUpDown1.Value = trackBar1.Value;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
trackBar1.Value = Convert.ToInt32(numericUpDown1.Value);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Color c = Color.FromArgb(0xFF, colImageBackground);
pictureBox1.BackColor = c;
}
}
}
Parent Form CS relevant section:
private void newPictureBox_Click(object sender, EventArgs e)
{
UserControl1 _UserControl = new UserControl1();
PictureBox _PictureBox = (PictureBox)sender;
string _NewControlClusterName = "_New" + _PictureBox.Name;
_UserControl.Name = _NewControlClusterName;
_UserControl.ThreadCount = 16;
_UserControl.ImageBackground = _PictureBox.BackColor;
_UserControl.Dock = DockStyle.Top;
_UserControl.PictureClick += new EventHandler(ClusterControl_Click);
//_UserControl.TrackBarScroll += new EventHandler(GetTartanCode);
panel3.Controls.Add(_UserControl);
panel3.Controls.SetChildIndex(_UserControl, 0);
}
And I am having intermittent issues with raising the click event to the parent form using this control.
I have tried everything I can find in Google and Stack Overflow with no joy. My questions are this:
Am I even in the right ballpark?
Is this something that needs to be coded in the parent form cs file?
Is this something that needs to be reconfigured in the composite control cs file?
Is this something that needs to be configured in both files?
I believe I have a solution.
What I was not doing was directly assigning the request to the control I wanted to register the event for. Instead I was assigning it to a new control and therefore nothing would happen.
public event EventHandler PictureClick
{
add { pictureBox1.Click += value; }
remove { pictureBox1.Click -= value; }
}
And so far, It works every time.
Is there an easy method to prompt the user to confirm a combo box selection change and not process the change if the user selected no?
We have a combo box where changing the selection will cause loss of data. Basically the user selects a type, then they are able to enter attributes of that type. If they change the type we clear all of the attributes as they may no longer apply. The problem is that to under the selection you raise the SelectionChanged event again.
Here is a snippet:
if (e.RemovedItems.Count > 0)
{
result = MessageBox.Show("Do you wish to continue?",
"Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.No)
{
if (e.RemovedItems.Count > 0)
((ComboBox)sender).SelectedItem = e.RemovedItems[0];
else
((ComboBox)sender).SelectedItem = null;
}
}
I have two solutions, neither of which I like.
After the user selects 'No', remove the SelectionChanged event handler, change the selected item and then register the SelectionChanged event handler again. This means you have to hold onto a reference of the event handler in the class so that you can add and remove it.
Create a ProcessSelectionChanged boolean as part of the class. Always check it at the start of the event handler. Set it to false before we change the selection back and then reset it to true afterwards. This will work, but I don't like using flags to basically nullify an event handler.
Anyone have an alternative solution or an improvement on the ones I mention?
I found this good implementation.
private bool handleSelection=true;
private void ComboBox_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (handleSelection)
{
MessageBoxResult result = MessageBox.Show
("Continue change?", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No)
{
ComboBox combo = (ComboBox)sender;
handleSelection = false;
combo.SelectedItem = e.RemovedItems[0];
return;
}
}
handleSelection = true;
}
source: http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html
Maybe create a class deriving from ComboBox, and override the OnSelectedItemChanged (Or OnSelectionChangeCommitted.)
Validating within the SelectionChanged event handler allows you to cancel your logic if the selection is invalid, but I don't know of an easy way to cancel the event or item selection.
My solution was to sub-class the WPF combo-box and add an internal handler for the SelectionChanged event. Whenever the event fires, my private internal handler raises a custom SelectionChanging event instead.
If the Cancel property is set on the corresponding SelectionChangingEventArgs, the event isn't raised and the SelectedIndex is reverted to its previous value. Otherwise a new SelectionChanged is raised that shadows the base event. Hopefully this helps!
EventArgs and handler delegate for SelectionChanging event:
public class SelectionChangingEventArgs : RoutedEventArgs
{
public bool Cancel { get; set; }
}
public delegate void
SelectionChangingEventHandler(Object sender, SelectionChangingEventArgs e);
ChangingComboBox class implementation:
public class ChangingComboBox : ComboBox
{
private int _index;
private int _lastIndex;
private bool _suppress;
public event SelectionChangingEventHandler SelectionChanging;
public new event SelectionChangedEventHandler SelectionChanged;
public ChangingComboBox()
{
_index = -1;
_lastIndex = 0;
_suppress = false;
base.SelectionChanged += InternalSelectionChanged;
}
private void InternalSelectionChanged(Object s, SelectionChangedEventArgs e)
{
var args = new SelectionChangingEventArgs();
OnSelectionChanging(args);
if(args.Cancel)
{
return;
}
OnSelectionChanged(e);
}
public new void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (_suppress) return;
// The selection has changed, so _index must be updated
_index = SelectedIndex;
if (SelectionChanged != null)
{
SelectionChanged(this, e);
}
}
public void OnSelectionChanging(SelectionChangingEventArgs e)
{
if (_suppress) return;
// Recall the last SelectedIndex before raising SelectionChanging
_lastIndex = (_index >= 0) ? _index : SelectedIndex;
if(SelectionChanging == null) return;
// Invoke user event handler and revert to last
// selected index if user cancels the change
SelectionChanging(this, e);
if (e.Cancel)
{
_suppress = true;
SelectedIndex = _lastIndex;
_suppress = false;
}
}
}
In WPF dynamically set the object with
if (sender.IsMouseCaptured)
{
//perform operation
}
I do not believe using the dispatcher to post (or delay) a property update is a good solution, it is more of a workaround that is not really needed. The following solution i fully mvvm and it does not require a dispatcher.
First Bind the SelectedItem with an Explicit binding Mode. //this enables us to decide whether to Commit using the UpdateSource() method the changes to the VM or to Revert using the UpdateTarget() method in the UI.
Next, add a method to the VM that confirms if the change is allowed (This method can contain a service that prompts for user confirmation and returns a bool).
In the view code behind hook to the SelectionChanged event and update the Source (i.e., the VM) or the Target (i.e. the V) in accordance to whether the VM.ConfirmChange(...) method returned value as follows:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(e.AddedItems.Count != 0)
{
var selectedItem = e.AddedItems[0];
if (e.AddedItems[0] != _ViewModel.SelectedFormatType)
{
var comboBoxSelectedItemBinder = _TypesComboBox.GetBindingExpression(Selector.SelectedItemProperty); //_TypesComboBox is the name of the ComboBox control
if (_ViewModel.ConfirmChange(selectedItem))
{
// Update the VM.SelectedItem property if the user confirms the change.
comboBoxSelectedItemBinder.UpdateSource();
}
else
{
//otherwise update the view in accordance to the VM.SelectedItem property
comboBoxSelectedItemBinder.UpdateTarget();
}
}
}
}
This is an old question, but after struggling with the issue time and again I came up with this solution:
ComboBoxHelper.cs:
public class ComboBoxHelper
{
private readonly ComboBox _control;
public ComboBoxHelper(ComboBox control)
{
_control = control;
_control.PreviewMouseLeftButtonDown += _control_PreviewMouseLeftButtonDown; ;
_control.PreviewMouseLeftButtonUp += _control_PreviewMouseLeftButtonUp; ;
}
public Func<bool> IsEditingAllowed { get; set; }
public Func<object, bool> IsValidSelection { get; set; }
public Action<object> OnItemSelected { get; set; }
public bool CloseDropDownOnInvalidSelection { get; set; } = true;
private bool _handledMouseDown = false;
private void _control_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var isEditingAllowed = IsEditingAllowed?.Invoke() ?? true;
if (!isEditingAllowed)
{
e.Handled = true;
return;
}
_handledMouseDown = true;
}
private void _control_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (!_handledMouseDown) return;
_handledMouseDown = false;
var fe = (FrameworkElement)e.OriginalSource;
if (fe.DataContext != _control.DataContext)
{
//ASSUMPTION: Click was on an item and not the ComboBox itself (to open it)
var item = fe.DataContext;
var isValidSelection = IsValidSelection?.Invoke(item) ?? true;
if (isValidSelection)
{
OnItemSelected?.Invoke(item);
_control.IsDropDownOpen = false;
}
else if(CloseDropDownOnInvalidSelection)
{
_control.IsDropDownOpen = false;
}
e.Handled = true;
}
}
}
It can be used in a custom UserControl like this:
public class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
var helper = new ComboBoxHelper(MyComboBox); //MyComboBox is x:Name of the ComboBox in Xaml
helper.IsEditingAllowed = () => return Keyboard.Modifiers != Modifiers.Shift; //example
helper.IsValidSelection = (item) => return item.ToString() != "Invalid example.";
helper.OnItemSelected = (item) =>
{
System.Console.WriteLine(item);
};
}
}
This is independent of the SelectionChanged event, there are no side effects of the event firing more often than required. So others can safely listen to the event, e.g. to update their UI. Also avoided: "recursive" calls caused by resetting the selection from within the event handler to a valid item.
The assumptions made above regarding DataContext may not be a perfect fit for all scenarios, but can be easily adapted. A possible alternative would be to check, if the ComboBox is a visual parent of e.OriginalSource, which it isn't when an item is selected.
the Text property of control on winform is always string type, so if i wanna expose property of other type for custom control, i have to do the conversion as following, if i have dozens of properties to expose, it will be such pain for me.
public int ImageGroupLength
{
get
{
return int.Parse(this.imageGroupLength.Text);
}
set
{
this.imageGroupLength.Text = value.ToString();
}
}
so, is there any elegant way to do the conversion?
Creating your own control is the way to go here. Add a new class to your project and paste the code shown below. Compile. The new control shows up on the top of your toolbox. You'll want to implement the BadValue event to warn the user that the entered text isn't suitable. And the ValueChanged is available to get an event when the Value property changes.
using System;
using System.Windows.Forms;
class ValueBox : TextBox {
public event EventHandler BadValue;
public event EventHandler ValueChanged;
private int mValue;
public int Value {
get { return mValue; }
set {
if (value != mValue) {
mValue = value;
OnValueChanged(EventArgs.Empty);
base.Text = mValue.ToString();
}
}
}
protected void OnValueChanged(EventArgs e) {
EventHandler handler = ValueChanged;
if (handler != null) handler(this, e);
}
protected void OnBadValue(EventArgs e) {
EventHandler handler = BadValue;
if (handler != null) handler(this, e);
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
base.Text = mValue.ToString();
}
protected override void OnValidating(System.ComponentModel.CancelEventArgs e) {
int value;
if (!int.TryParse(base.Text, out value)) {
SelectionStart = 0;
SelectionLength = base.Text.Length;
e.Cancel = true;
OnBadValue(EventArgs.Empty);
}
else base.OnValidating(e);
}
}
Have you considered subclassing the TextBox control and simply placing that on your custom control instead? You could create a new property that parses the input string and returns an integer.
Not exactly, but you can at-least get some safety in there by using something like this.
This will save you heart-ache when people try and put text into the length field!
public int ImageGroupLength
{
get
{
int ret;
int.TryParse(this.imageGroupLength.Text, out ret);
return ret; //Ret will be 0 if tryparse fails
}
set
{
...
}
}
Say I've got a button on a form that I want to disable if some condition is met. Is there a way to check for this condition inside the button's "IsEnabled" event handler and modify the enabled state such that setting the enabled state a second time does not trigger another call to the IsEnabled event handler?
Let me demonstrate:
private void ExportResults_IsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e)
{
if (some condition)
{
uxExportResults.IsEnabled = false; // this will cause another call to the event handler, eventually resulting in a stack overflow
}
}
Assume I'm triggering the event elsewhere (which I am).
if (someCondition && uxExportResults.IsEnabled) { ... }
This will only disable your control if it's enabled.
Another option is to temporarily disable the event like so:
private void ExportResults_IsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e)
{
if (some condition)
{
uxExportResults.IsEnabledChanged -= ExportResults_IsEnabledChanged;
try
{
uxExportResults.IsEnabled = false; // this will cause another call to the event handler, eventually resulting in a stack overflow
}
finally
{
uxExportResults.IsEnabledChanged += ExportResults_IsEnabledChanged;
}
}
}
Simplest solution is to check the value of IsEnabled before you set it.
private void ExportResults_IsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e)
{
if (uxExportResults.IsEnabled == true)
{
uxExportResults.IsEnabled = false;
}
}
Also, if you have the ability to change the code for the button, IsEnabled should not send the event unless the value actually changes.
public bool IsEnabled
{
get { return isEnabled; }
set
{
if(isEnabled != value)
{
isEnabled = value;
IsEnabledChanged(this,args);
}
}
}