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
{
...
}
}
Related
I have created various properties inside of a User Control, and have had great success with accessing and editing them. I'm now trying to set up events for a number of these to be raised when one of these properties is changed. I have tried the MSDN example code for doing this (see here), but it is giving me this error when I try to build the solution:
Severity Code Description Project File Line Suppression State
Error CS0079 The event 'AbilityScoreDisplay.AbilityTitleChanged' can only appear on the left hand side of += or -= DnD Character Sheet C:\Users\bradley beasley\Documents\Visual Studio 2019\Projects\DnD Character Sheet\DnD Character Sheet\AbilityScoreDisplay.Designer.cs 199 Active
Another issue that I am having is that I am struggling to figure out how to get that event to appear in the Visual Studio 2019 Designer Properties window.
Here is the code that I have added to the designer file:
namespace DnD_Character_Sheet
{
partial class AbilityScoreDisplay : UserControl
{
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
Invalidate();
}
}
public int AbilityModifier
{
get
{
return Convert.ToInt32(AbilityModifierTextBox.Text);
}
private set
{
if (value >= 0) AbilityModifierTextBox.Text = String.Format("+{0}", value);
else AbilityModifierTextBox.Text = value.ToString();
Invalidate();
}
}
public int AbilityScore
{
get
{
return Convert.ToInt32(AbilityScoreLabel.Text);
}
set
{
AbilityModifier = (int)(Math.Floor((double)(value) / 2)) - 5;
Invalidate();
}
}
private EventHandler onAbilityTitleChanged { get; set; }
private EventHandler onAbilityScoreChanged { get; set; }
public event EventHandler AbilityTitleChanged
{
add
{
onAbilityTitleChanged += value;
}
remove
{
onAbilityTitleChanged -= value;
}
}
public event EventHandler AbilityScoreChanged
{
add
{
onAbilityScoreChanged += value;
}
remove
{
onAbilityScoreChanged -= value;
}
}
protected virtual void OnAbilityTitleChanged(EventArgs e)
{
AbilityTitleChanged?.Invoke(this, e);
}
protected virtual void OnAbilityScoreChanged(EventArgs e)
{
AbilityScoreChanged?.Invoke(this, e);
}
}
}
The aim is to enable an event to be raised whenever a property is changed so that it can do other stuff elsewhere in the form that the controls will be in. I'm fairly certain that I am missing some very important stuff, or that my code is not that effective at all, but I am learning this kind of code for the first time, and I have tried many different things that have just not worked.
Any help at all would be greatly appreciated :)
I think you are confusing a few concepts. Let's do it step by step.
First, you need to be able to track event handlers:
private EventHandler _onAbilityTitleChanged;
You expose this event through a public property:
public event EventHandler AbilityTitleChanged
{
add
{
_onAbilityTitleChanged += value;
}
remove
{
_onAbilityTitleChanged -= value;
}
}
Finally, you need to fire the event so that all subscribed handlers can react to it. You can do so when the title changes (setter):
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
//Raising the event!
_onAbilityTitleChanged?.Invoke(this, new EventArgs());
}
}
Other classes can then subscribe to your event:
var control = new AbilityScoreDisplay();
control.AbilityTitleChanged += SomeHandlerForWhenTitleChanges;
private void SomeHandlerForWhenTitleChanges(object sender, EventArgs e)
{
//....
}
You might want to read up a bit on the INotifyPropertyChanged interface as well.
You typically do this by implementing INotifyPropertyChanged. This allows you to use one single event for all the properties. The property name is passed in the event arguments.
partial class AbilityScoreDisplay : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
}
In the properties do this (with AbilityModifier as an example):
private int _abilityModifier;
public int AbilityModifier
{
get { return _abilityModifier; }
private set {
if (value != _abilityModifier) {
_abilityModifier = value;
AbilityModifierTextBox.Text = value >= 0
? String.Format("+{0}", value)
: value.ToString();
OnPropertyChanged(nameof(AbilityModifier));
}
}
}
Assuming this event handler
private void ScoreDisplay_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
You can subscribe the event with
PropertyChanged += ScoreDisplay_PropertyChanged;
You need to use the add/remove syntax only in rare cases. Typically, when you create your own event store, because you have a lot of events and don't want to consume space for unsubscribed events.
You can use INotifyPropertyChanged together with data binding to immediately update the UI when changes are made to the data. To do this you would create a class with properties and the INotifyPropertyChanged implementation. In the form you then assign an instance of this class to the DataSource of a BindingSource. The controls are then bound to this BindingSource.
Then you can drop all the code used to read from or to write to text boxes or labels etc., as the binding mechanism does it automatically for you.
In XAML, i have a textblock
<TextBlock x:Name="block" Text="{Binding b1}"/>
and in c# i created a property
public int _b1;
public int b1
{
get { return _b1; }
set
{
_b1 = value;
}
}
public MainPage()
{
InitializeComponent();
block.DataContext = this;
}
this worked fine, textblock show the _b1. But when i add a button to chage the _b1 variable
private void bt_click(object sender, RoutedEventArgs e)
{
_b1 = 4;
}
the textblock didn't update ?????
To add to dotNet's answer (which is the correct answer), use a baseclass where you implement INotifyPropertyChanged if you want to avoid redundand code: (this is one example, there are other ways to implement this)
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value)) { return false; }
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And use it like so:
class MyClass: BindableBase
{
private int _b1;
public int B1
{
get { return _b1; }
set { SetProperty(ref _b1, value); }
}
}
For UI to update automatically upon property value change, your property needs to either be a DependencyProperty or your class needs to implement INotifyPropertyChanged interface.
For creating a DependencyProperty, you could use Visual Studio's propdp snippet (type propdp inside your class and press Tab) and fill in respective values. If you want to go INotifyPropertyChanged path, you'll need to write the following code in the setter of your property (AFTER setting the value of _b1):
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("b1"));
I read some about DataBinding, mostly complicated things like SQL or whatever XAML and stuff.
All I want my programm to do is, if the "value" of a variable changes just write it in a textbox or label. (using WindowsForms)
So far what I have:
namespace DataBinding_Test
{
public partial class Form1 : Form
{
BindingSource bs = new BindingSource();
Class1 test = new Class1();
public Form1()
{
InitializeComponent();
test.name = "Hello";
bs.DataSource = test;
label1.DataBindings.Add(new Binding("Text", bs, "name", false, DataSourceUpdateMode.OnPropertyChanged));
}
private void button1_Click(object sender, EventArgs e)
{
test.name = textBox1.Text;
}
}
}
Class1 just has a public property name. On startup lable1 will show my "Hello" string. Then on button click the name property will change. On debug I saw the actual DataSource of "bs" contains the new property value, but the label will not show anything...
Is there any realtivly easy way to do this?
The Backround is: periodically there will be a polling of sensor data throug RS232. If the value of one sensor changes I want to show this in label or textbox. Now a backroundthreaded timer will need invokes and stuff to access the GUI thread; thought this would be easier with databinding but seems not :P
Thanks to all, great site, great work! :)
Another way to make things work without implementing INotifyPropertyChanged
class Class1
{
private string name;
public string Name
{
get { return name; }
set
{
//Check if you are assigning the same value. Depends on your business logic
//here is the simplest check
if (Equals(name, value))
return;
name = value;
OnNameChanged();
}
public event EventHandler NameChanged;
protected virtual void OnNameChanged()
{
var handler = NameChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
}
The trick is to have event with the name combined by name of property and Changed suffix and to raise it whenever value of your property is changed
In order your code would work you should implement INotifyPropertyChanged interface in your binded class. Without it your binding simply doesn't know, when the change occures. There you should implenent the logic, according to which you would notify your subscribers about which when something changed in your class (the setter part) and what has changed (PropertyChangedEventArgs). See example for your class:
class Class1: INotifyPropertyChanged
{
private string name = "";
public string Name
{
get { return name; }
set { name = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
And change the property name from "name" to "Name" in your binding:
label1.DataBindings.Add(new Binding("Text", bs, "Name", false, DataSourceUpdateMode.OnPropertyChanged));
// create winforms project on form1 drag a textbox (testbox1)
// and a button (button1) with a button click event handler
// this updates the textbox when the button is clicked
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
MyClass Myobj = new MyClass();
public Form1()
{
InitializeComponent();
/* propertyname, datasource, datamember */
textBox1.DataBindings.Add("Text", Myobj, "Unit");
}
public class MyClass : INotifyPropertyChanged
{
private int unit = 3;
/* property change event */
public event PropertyChangedEventHandler PropertyChanged;
public int Unit
{
get
{
return this.unit;
}
set
{
if (value != this.unit)
{
this.unit = value;
NotifyPropertyChanged("Unit");
}
}
}
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Myobj.Unit += 4;
}
}
}
I created an extension method for this that I would like to share
Usage
private void Form1_Load(object sender, EventArgs e)
{
ResultLabel.Bind(NameTextBox);
WarningLabel.Bind(NameTextBox,i => i.Length == 0 ? "field required!" : "");
SendButton.Bind(NameTextBox, i => SendButton.Enabled = !(i.Length == 0));
}
Extension
public static class Extention
{
public static void Bind(this Control owner, Control dataSource)
{
List<EventInfo> fields = dataSource.GetType().GetEvents().ToList();
int index = fields.FindIndex(item => item.Name == "TextChanged");
if (index >= 0)
{
Control sender = dataSource as Control;
owner.Text = dataSource.Text;
dataSource.TextChanged += delegate (Object o, EventArgs e) { owner.Text = sender.Text; };
}
}
public static void Bind(this Control owner, Control dataSource, Func<string,string> onChange)
{
List<EventInfo> fields = dataSource.GetType().GetEvents().ToList();
int index = fields.FindIndex(item => item.Name == "TextChanged");
if (index >= 0)
{
Control sender = dataSource as Control;
owner.Text = onChange(sender.Text);
dataSource.TextChanged += delegate (Object o, EventArgs e) { owner.Text = onChange(sender.Text); };
}
}
public static void Bind(this Control owner, Control dataSource, Action<string> onChange)
{
List<EventInfo> fields = dataSource.GetType().GetEvents().ToList();
int index = fields.FindIndex(item => item.Name == "TextChanged");
if (index >= 0)
{
Control sender = dataSource as Control;
onChange(sender.Text);
dataSource.TextChanged += delegate (Object o, EventArgs e) { onChange(sender.Text); };
}
}
}
I'm not sure if that is what you want but you can can write whatever you variable contains into the Textbox or Label by using the control.Text property.
textBox1.Text ="Some other Text"
or
string variable = "Hello 2";
textBox1.Text = variable;
Why dou you want to use Databinding? Its mutch easier this way.
I just created a control with the following code:
public partial class KindsEditor : NaviGroupList, INotifyPropertyChanged
{
private WebBrowser _Browser;
private BasicProject _Project;
public event PropertyChangedEventHandler PropertyChanged;
public bool RequiredDataLoaded { get { return (Project != null) && (Browser != null); } }
private bool _ButtonsEnabled = false;
public bool ButtonsEnabled { set { SetButtonsEnabled(value); } get { return _ButtonsEnabled; } }
public WebBrowser Browser
{
get { return _Browser; }
set
{
_Browser = value;
OnPropertyChanged(new PropertyChangedEventArgs("Browser"));
OnPropertyChanged(new PropertyChangedEventArgs("RequiredDataLoaded"));
}
}
public BasicProject Project
{
get { return null; }
set { LoadProject(value); }
}
public KindsEditor()
{
InitializeComponent();
DataBindings.Add("ButtonsEnabled", this, "RequiredDataLoaded");
}
private void SetButtonsEnabled(bool value)
{
newKindButton.Enabled = value;
_ButtonsEnabled = value;
OnPropertyChanged(new PropertyChangedEventArgs("ButtonsEnabled"));
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
private void LoadProject(BasicProject value)
{
if (value != null) DataSource = value.Kinds;
_Project = value;
OnPropertyChanged(new PropertyChangedEventArgs("Project"));
OnPropertyChanged(new PropertyChangedEventArgs("RequiredDataLoaded"));
}
}
I removed some stuff that I think is irrelevant to my problem. I was trying to bind one button (newKindButton) being enabled to two properties (Browser and Project) being not null. I know it's messy and no one would expect that I call OnPropertyChanged while changing a different property and other stuff that might not be supposed to be done. I'll fix that later. But the weird thing is that the Form that uses this control (I drag and dropped it from the toolbox) added this line to the InitializeComponent() auto-generated code:
this.kindsEditor1.DataBindings.Add(new System.Windows.Forms.Binding("ButtonsEnabled", this.kindsEditor1, "RequiredDataLoaded", true));
So when I try to run the app I get an exception telling me that this line is trying to bind to the same property twice. I swear, I never added any binding from the properties panel. If I remove the line
DataBindings.Add("ButtonsEnabled", this, "RequiredDataLoaded");
from KindsEditor's constructor, the auto-generated line disappears. Anyone knows what's going on?
Try adding DesignerProperties.GetIsInDesignMode around the binding:
public KindsEditor()
{
InitializeComponent();
if (!DesignerProperties.GetIsInDesignMode(this))
DataBindings.Add("ButtonsEnabled", this, "RequiredDataLoaded");
}
I don't have a direct answer, but I suspect that Visual Studio thinks it needs to serialize something (the generated code) when it shouldn't. The above construct hides the binding from Visual Studio and only activates it during runtime.
How do you perform databinding against the MonthCalendar.SelectionRange property? Given the property is of type 'SelectionRange' which is a class I am not sure how to go about it. Any examples would be much appreciated.
Well, there don't seem to be any obvious events for this either on the MonthCalendar or the SelectionRange, and neither implements INotifyPropertyChanged, so it looks like data-binding might not be possible here.
Update: It does, however, raise the DateChanged, so you could hook some stuff together manually, or (more usefully) by subclassing the control to expose the values and events in a fashion suitable for binding. Note the Actual(...) are useful because the end (otherwise) is just before midnight, rather than midnight itself...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;
class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
Debug.WriteLine(ToString());
}
private void SetField<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
}
}
private DateTime start, end;
public DateTime Start { get { return start; } set { SetField(ref start, value, "Start"); } }
public DateTime End { get { return end; } set { SetField(ref end, value, "End"); } }
}
class BindableCalendar : MonthCalendar
{
public DateTime ActualSelectionStart
{
get { return SelectionRange.Start; }
set { if (ActualSelectionStart != value) { SetSelectionRange(value, ActualSelectionEnd); } }
}
public DateTime ActualSelectionEnd
{
get { return SelectionRange.End; }
set { if (ActualSelectionEnd != value) { SetSelectionRange(ActualSelectionStart, value); } }
}
// should really use EventHandlerList here...
public event EventHandler ActualSelectionStartChanged, ActualSelectionEndChanged;
DateTime lastKnownStart, lastKnownEnd;
protected override void OnDateChanged(DateRangeEventArgs drevent)
{
base.OnDateChanged(drevent);
if (lastKnownStart != drevent.Start)
{
if (ActualSelectionStartChanged != null) ActualSelectionStartChanged(this, EventArgs.Empty);
lastKnownStart = drevent.Start;
}
if (lastKnownEnd != drevent.End)
{
if (ActualSelectionEndChanged != null) ActualSelectionEndChanged(this, EventArgs.Empty);
lastKnownEnd = drevent.End;
}
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
MonthCalendar cal;
Button btn;
using (Form form = new Form
{
Controls = {
(cal = new BindableCalendar { Dock = DockStyle.Fill, MaxSelectionCount = 10 }),
(btn = new Button { Dock = DockStyle.Bottom, Text = "thwack"})
}
})
{
Foo foo = new Foo { Start = DateTime.Today, End = DateTime.Today.AddDays(1) };
cal.DataBindings.Add("ActualSelectionStart", foo, "Start").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
cal.DataBindings.Add("ActualSelectionEnd", foo, "End").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
btn.Click += delegate
{
foo.Start = foo.Start.AddDays(1);
foo.End = foo.End.AddDays(1);
};
Application.Run(form);
}
}
}
For me it seems to be very simple. I just bound SelectionStart and SelectionEnd properties of the MonthCalendar component.
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionStart",
bindingSource, "DateField", true));
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionEnd",
bindingSource, "DateField", true));