Best way to databind a group of radiobuttons in WinForms - c#

I'm currently working on databinding some of my existing Windows Forms, and I've ran into an issue figuring out the proper way of databinding a group of radiobutton controls within a group box.
My business object has an integer property which I want to databind against 4 radiobuttons (where each of them represents the values 0 - 3).
I'm currently binding against a presenter object which works as the binder between the form and the business object, and the way I've done it now is to have 4 separate properties which each binds against each of these values (I do use INotifyPropertyChanged, but not including that here):
Private int _propValue;
Public bool PropIsValue0
{
get { return _propValue == 0; }
set
{
if (value)
_propValue = 0;
}
}
Public bool PropIsValue1 { // As above, but with value == 1 }
Public bool PropIsValue2 { // As above, but with value == 2 }
Public bool PropIsValue3 { // As above, but with value == 3 }
And I then bind each of the radiobuttons to their respective property as above.
This does not seem right to me, so any advice are highly appreciated.

Following is a generic RadioGroupBox implementation in the spirit of ArielBH's suggestion (some code borrowed from Jay Andrew Allen's RadioPanel). Just add RadioButtons to it, set their tags to different integers and bind to the 'Selected' property.
public class RadioGroupBox : GroupBox
{
public event EventHandler SelectedChanged = delegate { };
int _selected;
public int Selected
{
get
{
return _selected;
}
set
{
int val = 0;
var radioButton = this.Controls.OfType<RadioButton>()
.FirstOrDefault(radio =>
radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val) && val == value);
if (radioButton != null)
{
radioButton.Checked = true;
_selected = val;
}
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButton;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
void radioButton_CheckedChanged(object sender, EventArgs e)
{
var radio = (RadioButton)sender;
int val = 0;
if (radio.Checked && radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val))
{
_selected = val;
SelectedChanged(this, new EventArgs());
}
}
}
Note that you can't bind to the 'Selected' property via the designer due to initialization order problems in InitializeComponent (the binding is performed before the radio buttons are initialized, so their tag is null in the first assignment). So just bind yourself like so:
public Form1()
{
InitializeComponent();
//Assuming selected1 and selected2 are defined as integer application settings
radioGroup1.DataBindings.Add("Selected", Properties.Settings.Default, "selected1");
radioGroup2.DataBindings.Add("Selected", Properties.Settings.Default, "selected2");
}

I know this post is old but in my search for an answer for this same problem I came across this post and it didn't solve my problem. I ended up having a lightbulb go off randomly just a minute ago and wanted to share my solution.
I have three radio buttons in a group box. I'm using a List<> of a custom class object as the data source.
Class Object:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BAL
{
class ProductItem
{
// Global Variable to store the value of which radio button should be checked
private int glbTaxStatus;
// Public variable to set initial value passed from
// database query and get value to save to database
public int TaxStatus
{
get { return glbTaxStatus; }
set { glbTaxStatus = value; }
}
// Get/Set for 1st Radio button
public bool Resale
{
// If the Global Variable = 1 return true, else return false
get
{
if (glbTaxStatus.Equals(1))
{
return true;
}
else
{
return false;
}
}
// If the value being passed in = 1 set the Global Variable = 1, else do nothing
set
{
if (value.Equals(true))
{
glbTaxStatus = 1;
}
}
}
// Get/Set for 2nd Radio button
public bool NeverTax
{
// If the Global Variable = 2 return true, else return false
get
{
if (glbTaxStatus.Equals(2))
{
return true;
}
else
{
return false;
}
}
// If the value being passed in = 2 set the Global Variable = 2, else do nothing
set
{
if (value.Equals(true))
{
glbTaxStatus = 2;
}
}
}
// Get/Set for 3rd Radio button
public bool AlwaysTax
{
// If the Global Variable = 3 return true, else return false
get
{
if (glbTaxStatus.Equals(3))
{
return true;
}
else
{
return false;
}
}
// If the value being passed in = 3 set the Global Variable = 3, else do nothing
set
{
if (value.Equals(true))
{
glbTaxStatus = 3;
}
}
}
// More code ...
Three seperate public variables with get/set accessing the same one global variable.
In the code behind, I have a function called during the Page_Load() setting all the controls databindings. For each radio button I add its own databinging.
radResale.DataBindings.Add("Checked", glbProductList, "Resale", true, DataSourceUpdateMode.OnPropertyChanged, false);
radNeverTax.DataBindings.Add("Checked", glbProductList, "NeverTax", true, DataSourceUpdateMode.OnPropertyChanged, false);
radAlwaysTax.DataBindings.Add("Checked", glbProductList, "Always", true, DataSourceUpdateMode.OnPropertyChanged, false);
I hope this helps someone!!

I think I would use my own GroupBox. I would bind the CustomGroupBox to your Model and set the correct RadioButton (using the tag or name properties) from the binded value.

This is my approach for binding a list of radio buttons to an enum.
Using the Enum as a string in the button's Tag property, I use the Binding.Format and Binding.Parse event to decide which button should be checked.
public enum OptionEnum
{
Option1 = 0,
Option2
}
OptionEnum _rbEnum = OptionEnum.Option1;
OptionEnum PropertyRBEnum
{
get { return _rbEnum; }
set
{
_rbEnum = value;
RaisePropertyChanged("PropertyRBEnum");
}
}
public static void FormatSelectedEnum<T>(object sender, ConvertEventArgs args) where T : struct
{
Binding binding = (sender as Binding);
if (binding == null) return;
Control button = binding.Control;
if (button == null || args.DesiredType != typeof(Boolean)) return;
T value = (T)args.Value;
T controlValue;
if (Enum.TryParse(button.Tag.ToString(), out controlValue))
{
args.Value = value.Equals(controlValue);
}
else
{
Exception ex = new Exception("String not found in Enum");
ex.Data.Add("Tag", button.Tag);
throw ex;
}
}
public static void ParseSelectedEnum<T>(object sender, ConvertEventArgs args) where T : struct
{
Binding binding = (sender as Binding);
if (binding == null) return;
Control button = binding.Control;
bool value = (bool)args.Value;
if (button == null || value != true) return;
T controlValue;
if (Enum.TryParse(button.Tag.ToString(), out controlValue))
{
args.Value = controlValue;
}
else
{
Exception ex = new Exception("String not found in Enum");
ex.Data.Add("Tag", button.Tag);
throw ex;
}
}
Then setup your data binding like this:
radioButton1.Tag = "Option1";
radioButton2.Tag = "Option2";
foreach (RadioButtonUx rb in new RadioButtonUx[] { radioButton1, radioButton2 })
{
Binding b = new Binding("Checked", this, "PropertyRBEnum");
b.Format += FormatSelectedRadioButton<OptionEnum>;
b.Parse += ParseSelectedRadioButton<OptionEnum>;
rb.DataBindings.Add(b);
}

I started to resolve the same problematic.
I used a RadioButtonBinding class which encapsulates all radiobuttons about an enum in the data source.
This following class keeps all radio buttons in a list and makes a lookup for the enum :
class RadioButtonBinding : ILookup<System.Enum, System.Windows.Forms.RadioButton>
{
private Type enumType;
private List<System.Windows.Forms.RadioButton> radioButtons;
private System.Windows.Forms.BindingSource bindingSource;
private string propertyName;
public RadioButtonBinding(Type myEnum, System.Windows.Forms.BindingSource bs, string propertyName)
{
this.enumType = myEnum;
this.radioButtons = new List<System.Windows.Forms.RadioButton>();
foreach (string name in System.Enum.GetNames(this.enumType))
{
System.Windows.Forms.RadioButton rb = new System.Windows.Forms.RadioButton();
rb.Text = name;
this.radioButtons.Add(rb);
rb.CheckedChanged += new EventHandler(rb_CheckedChanged);
}
this.bindingSource = bs;
this.propertyName = propertyName;
this.bindingSource.DataSourceChanged += new EventHandler(bindingSource_DataSourceChanged);
}
void bindingSource_DataSourceChanged(object sender, EventArgs e)
{
object obj = this.bindingSource.Current;
System.Enum item = obj.GetType().GetProperty(propertyName).GetValue(obj, new object[] { }) as System.Enum;
foreach (System.Enum value in System.Enum.GetValues(this.enumType))
{
if (this.Contains(value))
{
System.Windows.Forms.RadioButton rb = this[value].First();
if (value.Equals(item))
{
rb.Checked = true;
}
else
{
rb.Checked = false;
}
}
}
}
void rb_CheckedChanged(object sender, EventArgs e)
{
System.Windows.Forms.RadioButton rb = sender as System.Windows.Forms.RadioButton;
System.Enum val = null;
try
{
val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
}
catch(Exception ex)
{
// cannot occurred if code is safe
System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
}
object obj = this.bindingSource.Current;
obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] { });
this.bindingSource.CurrencyManager.Refresh();
}
public int Count
{
get
{
return System.Enum.GetNames(this.enumType).Count();
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.radioButtons.GetEnumerator();
}
public bool Contains(Enum key)
{
return System.Enum.GetNames(this.enumType).Contains(key.ToString());
}
public IEnumerable<System.Windows.Forms.RadioButton> this[Enum key]
{
get
{
return this.radioButtons.FindAll(a => { return a.Text == key.ToString(); });
}
}
IEnumerator<IGrouping<Enum, System.Windows.Forms.RadioButton>> IEnumerable<IGrouping<Enum, System.Windows.Forms.RadioButton>>.GetEnumerator()
{
throw new NotImplementedException();
}
public void AddControlsIntoGroupBox(System.Windows.Forms.GroupBox gb)
{
System.Windows.Forms.FlowLayoutPanel panel = new System.Windows.Forms.FlowLayoutPanel();
panel.Dock = System.Windows.Forms.DockStyle.Fill;
panel.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
foreach (System.Windows.Forms.RadioButton rb in this.radioButtons)
{
panel.Controls.Add(rb);
}
gb.Controls.Add(panel);
}
}
You are using the class into a form by adding that code in the constructor of the form:
public PageView()
{
InitializeComponent();
RadioButtonBinding rbWidth = new RadioButtonBinding(typeof(Library.EnumConstraint), this.pageBindingSource, "ConstraintWidth");
rbWidth.AddControlsIntoGroupBox(this.groupBox1);
RadioButtonBinding rbHeight = new RadioButtonBinding(typeof(Library.EnumConstraint), this.pageBindingSource, "ConstraintHeight");
rbHeight.AddControlsIntoGroupBox(this.groupBox3);
this.pageBindingSource.CurrentItemChanged += new EventHandler(pageBindingSource_CurrentItemChanged);
}

Set the tag name of your radio buttons to something that represents the value.
Create a string setting, for example, OptionDuplicateFiles, and give it the default value of the tag name for your default radio button.
To save your checked radio button:
Settings.Default.OptionDuplicateFiles = gbxDuplicateFiles.Controls
.OfType<RadioButton>()
.Where(b => b.Checked)
.Select(b => b.Tag)
.First()
.ToString();
To load your checked radio button:
(gbxDuplicateFiles.Controls
.OfType<RadioButton>()
.Where(b => b.Tag.ToString() == Settings.Default.OptionDuplicateFiles)
.First())
.Checked = true;
Tada!

I liked the idea of a RadioButtonGroupBox but I decided to create a version that is self supporting.
There is no reason to add value to Tag attribute or to introduce new value attributes.
Any assigned radio button is still a member of the RadioButtonGroupBox and the sequence of radiobuttons is defined during development.
Soo, I modified the code.
Now I can get and set the selected radiobutton by index position, By Control Name and by Text.
BTW Text is only useable if your asssigned Text is different for each radiobutton.
public class RadioButtonGroupBox : GroupBox
{
public event EventHandler SelectedChanged = delegate { };
int _nIndexPosCheckRadioButton = -1;
int _selected;
public int Selected
{
get
{
return _selected;
}
}
public int CheckedRadioButtonIndexPos
{
set
{
int nPosInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Set the RB that should be checked
if (nPosInList == value)
{
item.Checked = true;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosInList;
}
get
{
int nPosInList = -1;
int nPosCheckeItemInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Find the RB that is checked
if (item.Checked)
{
nPosCheckeItemInList = nPosInList;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosCheckeItemInList;
return _nIndexPosCheckRadioButton;
}
}
public string CheckedRadioButtonByText
{
set
{
int nPosInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Set the RB that should be checked
if (item.Text == value)
{
item.Checked = true;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosInList;
}
get
{
string cByTextValue = "__UNDEFINED__";
int nPosInList = -1;
int nPosCheckeItemInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Find the RB that is checked
if (item.Checked)
{
cByTextValue = item.Text;
nPosCheckeItemInList = nPosInList;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosCheckeItemInList;
return cByTextValue;
}
}
public string CheckedRadioButtonByName
{
set
{
int nPosInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Set the RB that should be checked
if (item.Name == value)
{
item.Checked = true;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosInList;
}
get
{
String cByNameValue = "__UNDEFINED__";
int nPosInList = -1;
int nPosCheckeItemInList = -1;
foreach (RadioButton item in this.Controls.OfType<RadioButton>())
{
// There are RadioButtonItems in the list...
nPosInList++;
// Find the RB that is checked
if (item.Checked)
{
cByNameValue = item.Name;
nPosCheckeItemInList = nPosInList;
// We can stop with the loop
break;
}
}
_nIndexPosCheckRadioButton = nPosCheckeItemInList;
return cByNameValue;
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButton;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
void radioButton_CheckedChanged(object sender, EventArgs e)
{
_selected = CheckedRadioButtonIndexPos;
SelectedChanged(this, new EventArgs());
}
}

My approach is to put each radio button into its own panel before binding them to a boolean property:
public static Binding Bind<TObject>(this RadioButton control, object dataSource, string dataMember)
{
// Put the radio button into its own panel
Panel panel = new Panel();
control.Parent.Controls.Add(panel);
panel.Location = control.Location;
panel.Size = control.Size;
panel.Controls.Add(control);
control.Location = new Point(0, 0);
// Do the actual data binding
return control.DataBindings.Add("Checked", dataSource, dataMember);
}

I would like to make an observation about the code block that might be helpful to people reading these posts. The following code may not always work as expected due to it's structure.
try
{
val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
}
catch(Exception ex)
{
// cannot occurred if code is safe
System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
}
object obj = this.bindingSource.Current;
obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] {
}
);
this.bindingSource.CurrencyManager.Refresh();
If an error occurs in the try block, the catch block will be executed. The code will continue to execute after the catch block. Since there was no handling of binding source, the variables following the catch could end up in a indeterminate state and may throw another exception that may or may not be handled.
A better approach is as follows
try
{
val = System.Enum.Parse(this.enumType, rb.Text) as System.Enum;
object obj = this.bindingSource.Current;
obj.GetType().GetProperty(propertyName).SetValue(obj, val, new object[] { });
this.bindingSource.CurrencyManager.Refresh();
}
catch(EntityException ex)
{
// handle error
}
catch(Exception ex)
{
// cannot occurred if code is safe
System.Windows.Forms.MessageBox.Show("No enum value for this radio button : " + ex.ToString());
}
This allows the enum value error to be handled as well as other errors that may occur. However use the EntityException or variations of it before the Exception block (all decendents of Exception have to come first). One can get specific entity state information for a entity framework error by using the entity framework classes instead of the Exception base class. This can be helpful for debugging or providing clearer run time messages for the user.
When I setup try-catch blocks I like to view it as a "layer" on top of the code. I make decisions about the flow of the exceptions through out the program, their display to the user, and what ever cleanup is required to allow the program to continue working properly without objects in a indeterminate state that can cascade to other errors.

I liked Jan Hoogma GroupBox binding but preferred more control over values you could bind to started by creating RadioButtonBIndable which can have values of different types assigned to it which are strongly typed.
public class RadioButtonBindable : RadioButton
{
public Int32 ValueInt { get; set; }
public Decimal ValueDecimal { get; set; }
public String ValueString { get; set; }
public Boolean ValueBoolean { get; set; }
}
Then I can work on the GroupBox and create GroupBoxBindable that can be bound to any of the values in the RadioButtonBindable (you could bind to multiple values e.g. ValueInt = 23 and ValueString = "Example Text"
You can also set a default value in case of teh unlikeley event where there is no RadioButton selected.
public class RadioButtonGroupBoxBindable : GroupBox, INotifyPropertyChanged
{
//public event EventHandler SelectedChanged = delegate { };
public event EventHandler ValueIntChanged = delegate { };
public event EventHandler ValueDecimalChanged = delegate { };
public event EventHandler ValueStringChanged = delegate { };
public event EventHandler ValueBooleanChanged = delegate { };
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Int32 DefaultValueInt { get; set; }
public Decimal DefaultValueDecimal { get; set; }
public String DefaultValueString { get; set; }
public Boolean DefaultValueBoolean { get; set; }
public Boolean ValueBoolean
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueBoolean == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
Boolean retVal = DefaultValueBoolean;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueBoolean;
break;
}
}
}
return retVal;
}
}
public int ValueInt
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueInt == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
int retVal = DefaultValueInt;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueInt;
break;
}
}
}
return retVal;
}
}
public decimal ValueDecimal
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueDecimal == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
decimal retVal = DefaultValueDecimal;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueDecimal;
break;
}
}
}
return retVal;
}
}
public string ValueString
{
set
{
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.ValueString == value)
{
item.Checked = true;
break;
}
}
}
}
get
{
string retVal = DefaultValueString;
foreach (Control control in this.Controls)
{
if (control is RadioButtonBindable)
{
RadioButtonBindable item = (RadioButtonBindable)control;
if (item.Checked)
{
retVal = item.ValueString;
break;
}
}
}
return retVal;
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButtonBindable;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
void radioButton_CheckedChanged(object sender, EventArgs e)
{
if (((RadioButtonBindable)sender).Checked)
{
OnPropertyChanged("ValueInt");
OnPropertyChanged("ValueDecimal");
OnPropertyChanged("ValueString");
OnPropertyChanged("ValueBoolean");
ValueIntChanged(this, new EventArgs());
ValueDecimalChanged(this, new EventArgs());
ValueStringChanged(this, new EventArgs());
ValueBooleanChanged(this, new EventArgs());
}
}
}

Related

Event for synchronizing all controls using the same Behavior

in my WPF application I have several forms with a multitude of input fields for the user. As it turns out not every user needs all fields depending on its companie's process therefore I have the new requirement to allow the user to hide fields depending on its own needs.
I was planning to use a Behavior for this which could be attached to basically every WPF-Control. The behavior would add a ContextMenu to each of the controls allowing to show/hide all of the available Fields. My current test project which seems to work nice has four DependencyProperties to make everything work:
string VisibilityGroupName:
This acts somehow as an Id for each field but is not unique in order to group multiple fields togther (eG a Label for a field caption to its corresponding TextBox). This string is currently also used as the name that the user sees in the ContextMenu.
Dictionary VisibilityDictionary:
This dictionary keeps track of all the visibility states of the fields. In my application I'm going to serialize this to XML in order to make the users decisions persistant.
bool AllowCustomVisibility:
This is just a flag in order to switch the whole funtionality off.
bool NotificationDummy:
This is where it gets interesting. Currently I abuse this property in conjunction with an ValueChanged-event to notify all the Controls that a state was changed so they can check if they are affected. While this works as expected I know that this is just a bad workaround because I don't know how the notification would be done correctly.
Does anybody have an idea how it is done correctly? I've marked the corresponding places in the code with TODOs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace CustomizableUserControlVisibility
{
public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
{
#region Fields
private Control _control = null;
private ContextMenu _contextMenu;
private bool _contextMenuIsBuilt = false;
#endregion
#region Properties
public bool NotificationDummy
{
get { return (bool)GetValue(NotificationDummyProperty); }
set { SetValue(NotificationDummyProperty, value); }
}
public static readonly DependencyProperty NotificationDummyProperty = DependencyProperty.Register("NotificationDummy", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public bool AllowCustomVisibility
{
get { return (bool)GetValue(AllowCustomVisibilityProperty); }
set { SetValue(AllowCustomVisibilityProperty, value); }
}
public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public string VisibilityGroupName
{
get { return (string)GetValue(VisibilityGroupNameProperty); }
set { SetValue(VisibilityGroupNameProperty, value); }
}
public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));
public Dictionary<string, bool> VisibilityDictionary
{
get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
set { SetValue(VisibilityDictionaryProperty, value); }
}
public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
#endregion
#region Constructor
public WPFCustomVisibilityBehavior()
{
// TODO: There should be a better way to notify other controls about state changes than this...
var temp = DependencyPropertyDescriptor.FromProperty(WPFCustomVisibilityBehavior.NotificationDummyProperty, typeof(WPFCustomVisibilityBehavior));
if (temp != null)
{
temp.AddValueChanged(this, OnNotificationDummyChanged);
}
}
#endregion
#region Overrrides
protected override void OnAttached()
{
base.OnAttached();
if (this.AllowCustomVisibility == false)
{
return;
}
this._control = this.AssociatedObject as Control;
if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
else
{
this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
}
// Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
if (this._control.ContextMenu == null && !(this._control is TextBox))
{
this._contextMenu = new ContextMenu();
ContextMenuService.SetContextMenu(this._control, this._contextMenu);
this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
}
}
}
#endregion
#region Event handling
private void ContextMenuOpening(ContextMenuEventArgs e)
{
if (this._contextMenuIsBuilt == false)
{
this._contextMenu.Items.Clear();
Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();
foreach (string k in this.VisibilityDictionary.Keys)
{
MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
menuItem.Click += MenuItem_Click;
menuItems.Add(k, menuItem);
}
var keyList = menuItems.Keys.ToList();
keyList.Sort();
foreach (string key in keyList)
{
this._contextMenu.Items.Add(menuItems[key]);
}
this._contextMenuIsBuilt = true;
}
foreach (MenuItem mi in this._contextMenu.Items)
{
mi.IsChecked = this.VisibilityDictionary[mi.Name];
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && this.VisibilityDictionary != null && this.VisibilityDictionary.ContainsKey(mi.Name))
{
this.VisibilityDictionary[mi.Name] = mi.IsChecked;
// TODO: There should be a better way to notify other controls about state changes than this...
this.NotificationDummy = !NotificationDummy;
}
}
private void OnNotificationDummyChanged(object sender, EventArgs args)
{
// TODO: There should be a better way to notify other controls about state changes than this...
if (this._control != null && this.VisibilityDictionary != null && !string.IsNullOrEmpty(this.VisibilityGroupName))
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
}
}
#endregion
}
}
Due to the lack of any other ideas I decided to use a static Event which seems to solve my problem quite well and this approach at leasts saves me the NotificationDummy-DependencyProperty which I had to use in the first place.
If anybody is interested - here is my final solution:
namespace CustomizableUserControlVisibility
{
public delegate void VisibilityChangedEventHandler(object visibilityDictionary);
public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
{
#region Fields
public static event VisibilityChangedEventHandler OnVisibilityChanged;
private Control _control = null;
private ContextMenu _contextMenu;
private bool _contextMenuIsBuilt = false;
#endregion
#region Properties
public bool AllowCustomVisibility
{
get { return (bool)GetValue(AllowCustomVisibilityProperty); }
set { SetValue(AllowCustomVisibilityProperty, value); }
}
public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));
public string VisibilityGroupName
{
get { return (string)GetValue(VisibilityGroupNameProperty); }
set { SetValue(VisibilityGroupNameProperty, value); }
}
public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));
public Dictionary<string, bool> VisibilityDictionary
{
get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
set { SetValue(VisibilityDictionaryProperty, value); }
}
public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
#endregion
#region Constructor
public WPFCustomVisibilityBehavior()
{
OnVisibilityChanged += VisibilityChanged;
}
#endregion
#region Overrrides
protected override void OnAttached()
{
base.OnAttached();
if (this.AllowCustomVisibility == false)
{
return;
}
this._control = this.AssociatedObject as Control;
if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
else
{
this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
}
}
// Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
if (this._control != null && this._control.ContextMenu == null && !(this._control is TextBox))
{
this._contextMenu = new ContextMenu();
ContextMenuService.SetContextMenu(this._control, this._contextMenu);
this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
}
}
#endregion
#region Event handling
private void ContextMenuOpening(ContextMenuEventArgs e)
{
if (this._contextMenuIsBuilt == false)
{
// Clear Items just to be sure there is nothing in it...
this._contextMenu.Items.Clear();
// Create default items first
MenuItem showAll = new MenuItem() { Header = "Show all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
showAll.Click += MenuItem_ShowAll_Click;
MenuItem hideAll = new MenuItem() { Header = "Hide all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
hideAll.Click += MenuItem_HideAll_Click;
// Create field items and sort them by name
Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();
foreach (string k in this.VisibilityDictionary.Keys)
{
MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
menuItem.Click += MenuItem_Click;
menuItems.Add(k, menuItem);
}
var keyList = menuItems.Keys.ToList();
keyList.Sort();
// Now add default items followed by field items
this._contextMenu.Items.Add(showAll);
this._contextMenu.Items.Add(hideAll);
this._contextMenu.Items.Add(new Separator());
foreach (string key in keyList)
{
this._contextMenu.Items.Add(menuItems[key]);
}
this._contextMenuIsBuilt = true;
}
foreach (Object o in this._contextMenu.Items)
{
MenuItem mi = o as MenuItem;
if (mi != null && mi.FontWeight != FontWeights.Bold)
{
mi.IsChecked = this.VisibilityDictionary[mi.Name];
}
}
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && this.VisibilityDictionary != null && this.VisibilityDictionary.ContainsKey(mi.Name))
{
this.VisibilityDictionary[mi.Name] = mi.IsChecked;
OnVisibilityChanged(this.VisibilityDictionary);
}
}
private void MenuItem_HideAll_Click(object sender, RoutedEventArgs e)
{
List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();
foreach (string key in keys)
{
this.VisibilityDictionary[key] = false;
}
OnVisibilityChanged(this.VisibilityDictionary);
}
private void MenuItem_ShowAll_Click(object sender, RoutedEventArgs e)
{
List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();
foreach (string key in keys)
{
this.VisibilityDictionary[key] = true;
}
OnVisibilityChanged(this.VisibilityDictionary);
}
private void VisibilityChanged(object visibilityDictionary)
{
if (this._control != null && this.VisibilityDictionary != null && this.VisibilityDictionary == visibilityDictionary && !string.IsNullOrEmpty(this.VisibilityGroupName))
{
if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
{
if (this.VisibilityDictionary[this.VisibilityGroupName])
{
this._control.Visibility = Visibility.Visible;
}
else
{
this._control.Visibility = Visibility.Collapsed;
}
}
}
}
#endregion
}
}

How to update a ListBox if an element was changed c#

Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)

DataGridView with ContextMenu assigned to column and a MessageBox

I have a DGV that has its datasource set to a BindingList. There is also a ContextMenu assigned to a column in the DGV. There is a MenuItem set on the ContextMenu that calls a MessageBox on the click event.
Everything works fine and the Methods are called and the MessageBox with YesNo responses do what they are susppose to.
The problem that I am having is that when the MessageBox's click event occurs (Yes or No) it does it's job and goes away. If the same routine is called a second time, it again does it's job with no problem, then reappears. If I click Yes or No it goes away. If I call it a third time the MessageBox appears again does its job and reappears twice. As if everytime it's being called its iterating and calling itself again that amount of times. This will occur for everytime it's called.
The BindingList is built using a Class with nested properties and all data elements are present.
I tried using just a blank MessageBox with no DialogResults and no change. I even tried using the DGV's RaiseListChangedEvents=false in the ContextMenu click event and the DGV's Cell Enter Click Event.
I've stepped through my code and and no matter what the Class with the nested properties always gets called and causes the ContextMenu's click event to be called again and again... I figure this is by design since a BindingList will always AutoUpdate when a cell's value is accessed or changed.
ContextMenu's Column is a Button and is readonly.
So how do I either catch the MessageBox after it's run the first time or stop the BindingList from auto updating. My List draws its data from a Web Reference and I handle updates through the methods provided from the API. The only reason I'm using a BindingList is because the DGV doesn't work with just a List .
Thank you for any help or guidance. (First time posting, but have gathered and used a lot of info from here)
Here's some code:
_requestsView.AutoGenerateColumns = false;
_edit.DataPropertyName = "RequestId";
_patient.DataPropertyName = "Patient";
_dateSubmitted.DataPropertyName = "Date";
_completedBy.DataPropertyName = "CompletedBy";
_completedOn.DataPropertyName = "CompletedOn";
_procedure.DataPropertyName = "Procedure";
_stat.DataPropertyName = "Stat";
_viewReport.DataPropertyName = "ViewReport";
_selectedSpecialist.DataPropertyName = "SelectedSpecialist";
_status.DataPropertyName = "Status";
_rate.DataPropertyName = "Rating";
_requestsView.DataSource = _requestsBinding;
// _cancelRequest_Click is ContextMenu MenuItem
void _cancelRequest_Click(object sender, EventArgs e)
{
MessageBox.Show("test");
}
private void _requestsView_CellEnter(object sender, DataGridViewCellEventArgs e)
{
if (_requestsView.CurrentRow != null)
if (_requestsView.CurrentRow.Cells["_viewReport"].Selected)
try
{
var requestNumber = (int)_requestsView.CurrentRow.Cells ["_viewReport"].Value;
var letter = Api.Client.getCompletedLetter(UseSession.SessionId, requestNumber);
var convertedLetter = Convert.FromBase64String(letter);
var requestNumberToString = Convert.ToString(requestNumber);
var tmpfile = System.IO.Path.Combine(System.IO.Path.GetTempPath(), requestNumberToString + #".pdf");
var view = new ViewLetter(requestNumberToString, tmpfile);
File.WriteAllBytes(tmpfile, convertedLetter);
view._pdf.LoadFile(tmpfile);
view._pdf.PerformLayout();
view._pdf.Refresh();
view._pdf.setShowToolbar(true);
view._pdf.setZoom(100);
view.Show();
view.Activate();
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
if (_requestsView.CurrentRow != null)
if (_requestsView.CurrentRow.Cells["_edit"].Selected)
_edit.ContextMenuStrip.Show(Cursor.Position.X, Cursor.Position.Y);
if (_requestsView.CurrentRow != null)
if (_requestsView.CurrentRow.Cells["_rate"].Selected)
_rate.ContextMenuStrip.Show(Cursor.Position.X, Cursor.Position.Y);
}
public class Requests
{
private int _requestId;
private DateTime _date;
private string _patient;
private string _completedBy;
private string _completedOn;
private string _procedure;
private string _stat;
private int _viewReport;
private Specialists _selectedSpecialist;
private string _status;
private int _rating;
public Requests()
{ }
public Requests(string stat)
{
_stat = stat;
}
public int RequestId
{
get { return _requestId; }
set { _requestId = value; }
}
public DateTime Date
{
get { return _date; }
set { _date = value; }
}
public string Patient
{
get { return _patient; }
set { _patient = value; }
}
public string CompletedBy
{
get { return _completedBy; }
set { _completedBy = value; }
}
public string CompletedOn
{
get { return _completedOn; }
set { _completedOn = value; }
}
public string Procedure
{
get { return _procedure; }
set { _procedure = value; }
}
public string Stat
{
get { return _stat; }
set { _stat = value; }
}
public int ViewReport
{
get { return _viewReport; }
set { _viewReport = value; }
}
public Specialists SelectedSpecialist
{
get { return _selectedSpecialist; }
set { _selectedSpecialist = value; }
}
public string Status
{
get { return _status; }
set { _status = value; }
}
public int Rating
{
get { return _rating; }
set { _rating = value; }
}
}
Just wanted to update this and close it. I figured out a work around that sets a boolean true or false during different stages of events being called. If the boolean is set to true I just do a return to get out of the methods.

Need a numeric only windows control TextBox [duplicate]

This question already has answers here:
How do I make a textbox that only accepts numbers?
(41 answers)
Closed 9 years ago.
I am creating an old-school dialog in c# using a System.Windows.Controls.TextBox .
Is there an easy way of limiting text input in this box to numeric only?
Thanks!
Just implement the onkeyup event handler and if the key pressed is not a Character.IsDigit then clear it.
http://msdn.microsoft.com/en-us/library/system.windows.controls.textbox.onkeyup(VS.95).aspx
You could consider using a MaskedTextBox, setting the Mask property accordingly.
This is an extract from my answer to an earlier question.
Add this class to your project
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class TextBoxFilter
{
[Flags()]
public enum Filters
{
None = 0,
Text = 1,
Numbers = 2,
AlphaNumeric = Filters.Text | Filters.Numbers,
Currency = 4,
All = Filters.Text | Filters.Numbers | Filters.Currency
}
Dictionary<TextBox, Filters> _keyFilter;
Dictionary<TextBox, string> _allowedKeys;
Dictionary<TextBox, string> _invalidKeys;
Dictionary<TextBox, Windows.Forms.KeyEventArgs> keyEventArgs;
private static string DecimalMark = Application.CurrentCulture.NumberFormat.NumberDecimalSeparator;
private static string NegativeMark = Application.CurrentCulture.NumberFormat.NegativeSign;
private static string CurrencySymb = Application.CurrentCulture.NumberFormat.CurrencySymbol;
private static string CurrencyDecimal = Application.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;
public TextBoxFilter()
{
_keyFilter = new Dictionary<TextBox, Filters>();
_allowedKeys = new Dictionary<TextBox, string>();
_invalidKeys = new Dictionary<TextBox, string>();
keyEventArgs = new Dictionary<TextBox, KeyEventArgs>();
}
//set & remove filter
public void SetTextBoxFilter(TextBox textBox, Filters filter)
{
SetTextBoxFilter(textBox, filter, AllowedKeys(textBox), InvalidKeys(textBox));
}
public void SetTextBoxFilter(TextBox textBox, string allowedKeys)
{
SetTextBoxFilter(textBox, Strings.Filter(textBox), allowedKeys, InvalidKeys(textBox));
}
public void SetTextBoxFilter(TextBox textBox, string allowedKeys, string invalidKeys)
{
SetTextBoxFilter(textBox, Strings.Filter(textBox), allowedKeys, invalidKeys);
}
public void SetTextBoxFilter(TextBox textBox, Filters filter, string allowedKeys, string invalidKeys)
{
if (!_keyFilter.ContainsKey(textBox)) {
//add the textbox and its filter if it does not exist in
//the collection of registered textboxes
_keyFilter.Add(textBox, filter);
_allowedKeys.Add(textBox, allowedKeys);
_invalidKeys.Add(textBox, invalidKeys);
keyEventArgs.Add(textBox, new System.Windows.Forms.KeyEventArgs(Keys.None));
//add the event handlers
textBox.KeyDown += KeyDownUp;
textBox.KeyUp += KeyDownUp;
textBox.KeyPress += KeyPress;
textBox.Validating += Validating;
textBox.Disposed += Disposed;
} else {
//change the filter of the textbox if it exists in
//the collection of registered textboxes
_keyFilter(textBox) = filter;
_allowedKeys(textBox) = allowedKeys;
_invalidKeys(textBox) = invalidKeys;
}
}
public void RemoveTextBoxFilter(TextBox textBox)
{
if (_keyFilter.ContainsKey(textBox)) {
_keyFilter.Remove(textBox);
_allowedKeys.Remove(textBox);
_invalidKeys.Remove(textBox);
keyEventArgs.Remove(textBox);
textBox.KeyDown -= KeyDownUp;
textBox.KeyUp -= KeyDownUp;
textBox.KeyPress -= KeyPress;
textBox.Validating -= Validating;
textBox.Disposed -= Disposed;
}
}
public bool ContainsTextBox(TextBox textBox)
{
return _keyFilter.ContainsKey(textBox);
}
//properties
public Filters Filter {
get {
if (ContainsTextBox(textBox)) {
return _keyFilter.Item[textBox];
} else {
return Filters.None;
}
}
set { SetTextBoxFilter(textBox, value); }
}
public string AllowedKeys {
get {
if (ContainsTextBox(textBox)) {
return _allowedKeys(textBox);
} else {
return "";
}
}
set { SetTextBoxFilter(textBox, this.Filter(textBox), value, this.InvalidKeys(textBox)); }
}
public string InvalidKeys {
get {
if (ContainsTextBox(textBox)) {
return _invalidKeys(textBox);
} else {
return "";
}
}
set { SetTextBoxFilter(textBox, this.Filter(textBox), this.AllowedKeys(textBox), value); }
}
//event handlers
private void Disposed(object sender, System.EventArgs e)
{
RemoveTextBoxFilter((TextBox)sender);
}
private void KeyDownUp(object sender, System.Windows.Forms.KeyEventArgs e)
{
//assign the modifiers
keyEventArgs((TextBox)sender) = e;
}
private void KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
//ensure key pressed is in the allowed keys
object txt = (TextBox)sender;
object c = e.KeyChar;
bool allowKey = IsValidChar(txt, c, txt.SelectionStart);
//check for backspace & Ctrl combinations if the allowKey is still false
if (allowKey == false) {
if (keyEventArgs(txt).Control) {
//control modifier goes with A, X, C, V and Z for
//Select All, Cut, Copy, Paste and Undo respectively
object key = keyEventArgs(txt).KeyCode;
allowKey = (key == Keys.A || key == Keys.X || key == Keys.C || key == Keys.V || key == Keys.Z);
} else if (keyEventArgs(txt).KeyCode == Keys.Back) {
//allow the backspace key
allowKey = true;
}
}
//disable the key if it was not valid
if (!allowKey) {
e.Handled = true;
Interaction.Beep();
}
}
private void Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
object box = (TextBox)sender;
object boxFlags = _keyFilter(box);
//skip validation if the textbox allows all entries or there is no text
if (boxFlags == Filters.All | string.IsNullOrEmpty(box.Text))
return;
//otherwise check the characters entered
object txtChars = box.Text.ToCharArray;
bool isValidEntry = false;
//check each caracter for an invalid entry
for (i = 0; i <= txtChars.Length - 1; i++) {
object c = txtChars(i);
isValidEntry = IsValidChar(box, txtChars(i), i);
if (!isValidEntry) {
box.Select(i, 1);
break; // TODO: might not be correct. Was : Exit For
}
}
if (!isValidEntry)
e.Cancel = true;
if (!isValidEntry) {
Interaction.MsgBox("The text entered is invalid for the format " + boxFlags.ToString + "." + !string.IsNullOrEmpty(_allowedKeys(box)) ? Constants.vbCrLf + "Additional Allowed Keys: " + _allowedKeys(box) : "" + !string.IsNullOrEmpty(_invalidKeys(box)) ? Constants.vbCrLf + "Additional Invalid Keys: " + _invalidKeys(box) : "", MsgBoxStyle.Critical, "Invalid Entry");
}
}
private bool IsValidChar(TextBox textBox, char c, int charIndex)
{
//ensure key pressed is in the allowed keys
object pF = _keyFilter(textBox);
object aK = _allowedKeys(textBox);
object iK = _invalidKeys(textBox);
bool shouldAllow = false;
//if filter is set to all, return true unconditionally
if (pF == Filters.All)
return true;
//check preset filters
//check for text
if (EnumHasFlag(pF, Filters.Text)) {
if (!char.IsDigit(c)) {
shouldAllow = true;
} else {
//if the character is a digit, check for the number flag (AlphaNumerics)
if (EnumHasFlag(pF, Filters.Numbers)) {
shouldAllow = true;
}
}
}
//check for nubers
if (shouldAllow == false && EnumHasFlag(pF, Filters.Numbers)) {
if (char.IsDigit(c)) {
shouldAllow = true;
} else if (DecimalMark.Contains(c)) {
//allow the decimal if there is no decimal in the textbox's
//text or the selected text contains the mark
if (!textBox.Text.Substring(0, charIndex).Contains(c) || textBox.SelectedText.Contains(c)) {
shouldAllow = true;
}
} else if (NegativeMark.Contains(c) && (charIndex <= NegativeMark.IndexOf(c))) {
//allow the negative mark if we are at the start of the
//textbox
shouldAllow = true;
}
}
//check for currency
if (shouldAllow == false && EnumHasFlag(pF, Filters.Currency)) {
if (char.IsDigit(c)) {
shouldAllow = true;
} else if (CurrencyDecimal.Contains(c)) {
//allow the currency decimal mark if it does not exist in the
//textbox's text or the selected text contains the mark
if (!textBox.Text.Substring(0, charIndex).Contains(c) || textBox.SelectedText.Contains(c)) {
shouldAllow = true;
}
} else if (CurrencySymb.Contains(c) && (charIndex <= CurrencySymb.IndexOf(c))) {
//allow the currency symbol if we are in a valid position
shouldAllow = true;
}
}
//now check for extra allowed keys
if (!shouldAllow) {
shouldAllow = aK.Contains(c);
}
//and then check for extra invalid keys
if (shouldAllow && iK.Contains(c)) {
shouldAllow = false;
}
return shouldAllow;
}
[System.Diagnostics.DebuggerStepThrough()]
private bool EnumHasFlag(Enum value, Enum flag)
{
return (Convert.ToInt64(value) & Convert.ToInt64(flag)) == Convert.ToInt64(flag);
}
}
and then use it in your form as follows
public class Form1
{
TextBoxFilter filter = new TextBoxFilter();
private void Form1_Load(object sender, System.EventArgs e)
{
filter.SetTextBoxFilter(TextBox1, TextBoxFilter.Filters.Numbers);
}
public Form1()
{
Load += Form1_Load;
}
}
Also consider whether your design needs to restrict user input when typed/pasted or if it's OK to simply convert the user input to a number when he leaves the textbox.
Often the restrictive approach is harder to do (dealing with commas, periods, currency symbols, spaces, etc... can be a huge pain)
This method then becomes as simple as:
private void textBox1_Leave(object sender, EventArgs e)
{
textBox1.Text = VerifyNumeric(textBox1.Text);
}
private string VerifyNumeric(string text)
{
double value = 0;
double.TryParse(text, out value);
return value.ToString(); // could format here too.
}

Data binding not updating

I created a simple example with data binding (unfortunately we have a similar case in our system). I created a funky combo box:
public class FunkyComboBox : ComboBox
{
private object currentValue = null;
public FunkyComboBox()
{
if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
this.Items.Add("Other...");
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (!this.Text.StartsWith("Other") && currentValue != this.SelectedItem)
{
currentValue = this.SelectedItem;
BindingManagerBase bindingManager = DataManager;
base.OnSelectedIndexChanged(e);
}
}
protected override void OnSelectionChangeCommitted(EventArgs e)
{
string itemAsStr = this.SelectedItem != null ? SelectedItem.ToString() : "";
if (itemAsStr.StartsWith("Other"))
{
string newItem = "item" + this.Items.Count;
if (!Items.Contains(newItem))
{
Items.Add(newItem);
}
SelectedItem = newItem;
}
else
{
OnSelectedIndexChanged(e); //forces a selectedIndexChanged event to be thrown
base.OnSelectionChangeCommitted(e);
}
}
}
Which adds new items when you click Other (in our system it opens a form where you can query the database, etc). Then I have a simple data object:
public class MyClass
{
private string value;
public string MyData
{
get{ return value;}
set{ this.value = value;}
}
}
And a test form with two controls bound to this object (some designer code removed):
public partial class Form1 : Form
{
MyClass myObj = new MyClass();
public Form1()
{
InitializeComponent();
myObj.MyData = "Nothing";
myClassBindingSource.DataSource = myObj;
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.textBox1 = new System.Windows.Forms.TextBox();
this.myClassBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.funkyComboBox1 = new DataBindingTests.FunkyComboBox();
((System.ComponentModel.ISupportInitialize)(this.myClassBindingSource)).BeginInit();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
//
// myClassBindingSource
//
this.myClassBindingSource.DataSource = typeof(DataBindingTests.MyClass);
//
// funkyComboBox1
//
this.funkyComboBox1.DataBindings.Add(new System.Windows.Forms.Binding("SelectedItem", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
this.funkyComboBox1.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", this.myClassBindingSource, "MyData", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
//
// Form1
//
this.Controls.Add(this.textBox1);
this.Controls.Add(this.funkyComboBox1);
((System.ComponentModel.ISupportInitialize)(this.myClassBindingSource)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
private FunkyComboBox funkyComboBox1;
private System.Windows.Forms.BindingSource myClassBindingSource;
private System.Windows.Forms.TextBox textBox1;
}
If you run this code and start playing with the combo box, you will notice that the edit box changes only if you click on it. After every change a null value is set to my object and the text box is cleared. How can I make it set the correct value after every change?
I'm not really sure why the ComboBox data binding behaves this way, but I have found a workaround. It seems as though the databinding doesn't work correctly if you don't use a datasource for your ComboBox's values.
I made a few minor changes to FunkyComboBox, and it now works as expected.
public class FunkyComboBox : ComboBox
{
private object currentValue = null;
private List<string> innerItems = new List<string>();
public FunkyComboBox()
{
if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
innerItems.Add("Other...");
this.DataSource = innerItems;
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (!this.Text.StartsWith("Other") && currentValue != this.SelectedItem)
{
currentValue = this.SelectedItem;
BindingManagerBase bindingManager = DataManager;
base.OnSelectedIndexChanged(e);
}
}
protected override void OnSelectionChangeCommitted(EventArgs e)
{
string itemAsStr = this.SelectedItem != null ? SelectedItem.ToString() : "";
if (itemAsStr.StartsWith("Other"))
{
string newItem = "item" + this.Items.Count;
if(!innerItems.Contains(newItem))
{
innerItems.Add(newItem);
this.RefreshItems();
} SelectedItem = newItem;
}
else
{
OnSelectedIndexChanged(e);
//forces a selectedIndexChanged event to be thrown
base.OnSelectionChangeCommitted(e);
}
}
}
It seems to be a bug with the base ComboBox as well. It is not possible to get this binding source craziness to work correctly.

Categories