Windows Forms application, how to communicate between custom controls? - c#

I have three user controls. Here are the description of them:
1) First user control(ucCountry) contains combobox which displays country names from an xml file.
2) Second user control(ucChannelType) contains two radio buttons one to select TV and other to select Radio Channel Type.
3) Third usercontrol(ucChannels) will populate all the channels where country name is provided by ucCountry and type provided by ucChannelType
Now, how to communicate between these user control in a form. I need to decouple the usercontrols from the form. So, I need to use events. But if ucCountry fires an event (say CountryChanged event) and ucChannels subscribe the event, how to get the channel type from ucChannelType.
Thanks in advance...

best solution is to add properties to your custom controls. there may be no fields in background, the getters will just get data from property of internal standard control.

You could either have a property on ucCountry which provides the currently selected country. Something like:
public Country SelectedCountry
{
get
{
return (Country) myComboBox.SelectedItem;
}
}
And then when the event fires, the other controls go a get the property.
The other option is to use a custom event delegate, so the event handler for ucCountry.CountryChanged would have a country parameter:
public delegate void CountryChangedDelegate(Country sender)
public event CountryChangedDelegate CountryChanged;
Event handler in ucChannels:
public void ucCountry_CountryChanged(Country sender)
{
//do something with the new country
}
And wire the event to ucChannels:
myUcCountry.CountryChanged += new CountryChangedDelegate(ucCountry_CountryChanged);

You need to have public properties for the controls supplying data, and a public method to register events to for the control consuming the data. Here's a quick example:
public static void Test()
{
var a = new A();
var b = new B();
var c = new C() {a = a, b = b};
a.OnChange += c.Changed;
b.OnChange += c.Changed;
a.State = "CA";
b.ChannelType = "TV";
}
class A
{
private string _state;
public string State
{
get { return _state; }
set
{
_state = value;
if (OnChange != null) OnChange(this, EventArgs.Empty);
}
}
public event EventHandler<EventArgs> OnChange;
}
class B
{
private string _channelType;
public string ChannelType
{
get { return _channelType; }
set
{
_channelType = value;
if (OnChange != null) OnChange(this, EventArgs.Empty);
}
}
public event EventHandler<EventArgs> OnChange;
}
class C
{
public A a { get; set; }
public B b { get; set; }
public void Changed(object sender, EventArgs e)
{
Console.WriteLine("State: {0}\tChannelType: {1}", a.State, b.ChannelType);
}
}

Related

:How to create a event in c# without delegates?

I tried like this but it will return null values,
public event EventHandler<EventArgs> myevent;
public void Btn_Click(object sender, EventArgs e)
{
if (myevent!= null) //Here I get null value.
myevent(null, new EventArgs());
}
How to achieve the event fire?
Edit:
I have a UserControl in that user control which contain button event,inside the ButtonClick method I created this event.
I have a Form. In that form i m using this UserControl. So i need to Fire a event from User Control button click event to Form page particular function.
I wrote up a very simple, and basic solution for you. Please read the comments in the code to make sense of the solution. If anything is unclear, please ask questions.
Below sample, will cause the event to fire, if the person's name changes:
Here is the Person object:
public class Person
{
//Event to register to, when you want to capture name changed of person
public event EventHandler<NameChangedEventArgs> nameChangedEvent;
//Property
private string _name;
public string Name
{
get { return _name; }
set
{
this._name = value;
//Call the event. This will trigger the OnNameChangedEvent_Handler
OnNameChangedEvent_Handler(new NameChangedEventArgs() {NewName = value});
}
}
private void OnNameChangedEvent_Handler(NameChangedEventArgs args)
{
//Check if event is null, if not, invoke it.
nameChangedEvent?.Invoke(this, args);
}
}
//Custom event arguments class. This is the argument type passed to your handler, that will contain the new values/changed values
public class NameChangedEventArgs : EventArgs
{
public string NewName { get; set; }
}
Here is the code that instantiates and uses the Person object:
class Program
{
static void Main(string[] args)
{
//Instantiate person object
var person = new Person();
//Give it a default name
person.Name = "Andrew";
//Register to the nameChangedEvent, and tell it what to do if the person's name changes
person.nameChangedEvent += (sender, nameChangedArgs) =>
{
Console.WriteLine(nameChangedArgs.NewName);
};
//This will trigger the nameChanged event.
person.Name = "NewAndrewName";
Console.ReadKey();
}
}
can you please try the below way of calling event :
public event EventHandler myevent;
myevent += new EventHandler(Button1_Click);
if (myevent != null) //Here I get null value.
myevent(1, new EventArgs());

Passing data from textbox to datagridview

I am working in Visual Studio running a Windows application.
I am wondering if I can fill a DataGridView from a TextBox, that was a passed value itself?
For example, the user would search for a patient from a dialog form. The patient's name they select would populate a TextBox on my main form. I want that selected patients prior test history to populate a DataGridView on that main form within a tab.
Is this possible, if so how would I accomplish this?
It is possible. I would suggest setting up some sort of data binding. More specifically you will want some class that maintains state and data binds to your controls and possibly your dialog form. I don't know how much you are looking for so this might be going overboard but I would suggest something like this:
public class MainForm : Form
{
public MainForm(StateManager stateManager)
{
_stateManager = stateManager;
//data binding for your text box
txtPatientName.DataBindings.Add(nameof(txtPatientName.Text), stateManager, nameof(stateManager.PatientName));
//data binding for your grid
historyGrid.DataSource = stateManager.History;
}
private void btnShowForm_Click(object sender, EventArgs e)
{
using(var form = new DialogForm())
{
var result = form.ShowDialog();
if(result == DialogResult.Ok)
{
_stateManager.UpdatePatient(form.InputPatientName);
}
}
}
private StateManager _stateManager;
}
//this is the form where you enter the patient name
public class DialogForm : Form
{
//this holds the value where the patient's name is entered on the form
public string InputPatientName { get; set; }
}
//this class maintains your state
public class StateManager : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string PatientName
{
get { return _patientName; }
set
{
_patientName = value;
OnPropertyChanged(nameof(PatientName));
}
}
public BindingList<MedicalHistoryItems> History => _history ?? (_history = new BindingList<MedicalHistoryItems>());
public void UpdatePatient(string patientName)
{
History.Clear();
var historyRetriever = new HistoryRetriever();
History.AddRange(historyRetriever.RetrieveHistory(patientName));
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(propertyName);
}
private BindingList<MedicalHistoryItems> _history;
private string _patientName;
}

DataGrid calculated columns

I am trying to transfer my excel app to WPF datagrid. I am going to enter data to Column A and in column B I would like to make calculation taking previus cell and current cell of A column and add Column B previus cell.
calculation example : B2 = B1 + (A2-A1). What is best approach of doing so?
Personally, I'd start by creating a class that represents the records and implement INotifyPropertyChanged on that class.
public class recordObject : INotifyPropertyChanged
{
private int a;
public int A
{
get
{
return a;
}
set
{
a = value;
OnPropertyChanged("A");
}
}
private int b;
public int B
{
get
{
return b;
}
set
{
b = value;
OnPropertyChanged("B");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, in your code behind on the window you're showing the datagrid, you'll want to subscribe to PropertyChanged on each object in the list. Then you'd have to manually compute the column values whenever those properties changed. Ick, I know, but it'd work.
The property changed event would look like:
void recordObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var objectList = DataGrid.ItemsSource as List<recordObject>;
var myRecord = sender as recordObject;
if (objectList != null && myRecord != null)
{
int idx = objectList.IndexOf(myRecord);
// Perform your calculations here using idx to access records before and after the current record
// making sure to check for list boundaries for top and bottom.
// Also note that this will likely kick off cascading event calls so make sure you're only changing
// the previous or following record object.
}
}
If you hook this event onto all the records in your bound list, then it'll fire whenever any property is changed. In the class above, that'd apply to both A and B. You can filter which properties you're interested in monitoring through e.PropertyName (a simple string comparison) and guage the business logic accordingly. If you want to maintain encapsulation, or at least, put the business logic for the object on the object itself, this method could be a static one on the class recordObject. You'd have to provide for getting hold of the datagrid from that static method, though (likely through a static property on your window). So:
public static void recordObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
and connected so:
record.PropertyChanged += new PropertyChangedEventHandler(recordObject.recordObject_PropertyChanged);
The best thing is to implement that logic in a class, and bind the grid to the respective properties. For instance:
class SomeData
{
int A { get; set; }
int B { get; set; }
int AminusB { get { return A - B; } }
}

How to make own event handler?

I am making a windows forms project in C#, in which I made a class LabelX which inherits System.Windows.Forms.Label, then added a property Mass of float type
Now, my question is how can I handle, when value of Mass is changed.
e.g.:
When user enter value zero or less than zero
I want to fire a message that "Mass can't be zero or negative"
If I am interpreting this correctly, there are two parts to this. First, you need to detect invalid values and throw exceptions. Second, you need to raise an event when the property changes. This can be achieved as follows.
private float mass;
public float Mass
{
get
{
return this.mass;
}
set
{
if (value <= 0.0F)
{
throw new ArgumentOutOfRangeException("Mass cannot be zero or negative.");
}
if (this.mass != value)
{
this.mass = value;
OnMassChanged(EventArgs.Empty);
}
}
}
public event EventHandler MassChanged;
protected virtual void OnMassChanged(EventArgs args)
{
var handler = this.MassChanged;
if (handler != null)
{
handler(this, args);
}
}
To show a message if an invalid entry is made, you should put a try \ catch block around the call to set Mass and catch the ArgumentOutOfRangeException.
Try the following:
// Created an empty form with a LabelX control on it.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// Added this event from the property manager.
private void labelX1_MassChanged(object sender, EventArgs e)
{
var label = (LabelX)sender;
if (label.Mass <= 0.0)
MessageBox.Show("Mass is less than or equal to 0");
}
}
public class LabelX : Label
{
private float _mass;
public float Mass
{
get { return _mass; }
set
{
if (!value.Equals(_mass))
{
_mass = value;
OnMassChanged(EventArgs.Empty);
}
}
}
public event EventHandler MassChanged;
protected virtual void OnMassChanged(EventArgs e)
{
if (MassChanged != null)
MassChanged(this, e);
}
}
Outside of your LabelX class, create the following class:
public class MassChangedEventArgs : EventArgs
{
public float Mass { get; private set; }
public MassChangedEventArgs(float mass)
{
this.Mass = mass;
}
}
Also outside of your LabelX class, create the following delegate. This will be your event handler.
public delegate void MassChangedEventHandler(object sender, MassChangedEventArgs e);
Within your LabelX class, create an event to broadcast:
public class LabelX
{
public event MassChangedEventHandler MassChanged;
//the rest of your code here...
}
You'll also want to create a private instance method that will fire your event.
public class LabelX
{
public event MassChangedEventHandler MassChanged;
private void OnMassChanged()
{
if(MassChanged!=null)
this.MassChanged(this, new MassChangedEventArgs(this.Mass));
}
//the rest of your code here...
}
Finally, whenever your Mass property changes, call OnMassChanged. For instance:
public class LabelX
{
private float mass;
public float Mass
{
get
{
return mass;
}
set
{
mass = value;
OnMassChanged();
}
}
public event MassChangedEventHandler MassChanged;
private void OnMassChanged()
{
if(MassChanged!=null)
this.MassChanged(this, new MassChangedEventArgs(this.Mass));
}
//the rest of your code here...
}
When you want to handle that event on a per-instance basis, you just have to register a listener with the MassChanged event of your underlying object and perform whatever actions are necessary.
Events are a common pattern used in the framework. The process typically involves defining a delegate to be used as the event handlers, declaring the event using the handler, defining methods to raise the event, then hooking up to the properties the logic to raise the event.
The message you describe is better done as an Exception but here's an example to define the MassChanged event.
// Define event args if you have additional
// information to pass to your event handlers
public class MassChangedEventArgs : EventArgs
{
public MassChangedEventArgs(int oldMass)
{
OldMass = oldMass;
}
public int OldMass { get; private set; }
}
public class SomeObject
{
// There's a generic event handler delegate that can be
// used so you only need to define the event arguments.
public event EventHandler<MassChangedEventArgs> MassChanged;
// Convenience method to raise the event
protected virtual void OnMassChanged(MassChangedEventArgs e)
{
if (MassChanged != null)
MassChanged(this, e);
}
public int Mass
{
get
{
return mass;
}
set
{
// Your checks here
if (value <= 0)
throw new ArgumentOutOfRangeException("Mass", "Mass can't be zero or negative");
// Should only raise the event if the new value is different
if (value != mass)
{
// Change the mass
MassChangedEventArgs e = new MassChangedEventArgs(mass);
mass = value;
// Raise the event
OnMassChanged(e);
}
}
}
private int mass;
}
After that, it's just a matter of registering handlers to the event and going from there.
I am quite sure you you would like to 'fire' an exception in your case.
This more of a validation logic issue such AOP code contracts concept.
But if you really like to create an event for it you have to at least:
1) create an event storage variable in your label class
public event EventHandler MassChanged;
2) in your property (note that you loose the ability to use code gen functions of c# 3 for
which 'auto' implement the field to store your Mass property value)
public bool Mass
{
get { return _mass; }
set {
// check if value is invalid (0 or less) && that event subscribers exist
if(value<=0 && MassChanged != null) { MassChanged(this, null); }
else // otherwise assign ...
{
_mass = value;
}
}
}
3) create an event handler of type EventHandler
Best to read the msdn article for events: link text
Again I am pretty sure you are not handling exceptions properly in the app
if you need an event for this. I mean there is nothing wrong but events are
usually not used as means of value validations.

C# - Binding TextBox to an integer

How to bind a TextBox to an integer? For example, binding unit to textBox1.
public partial class Form1 : Form
{
int unit;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
textBox1.DataBindings.Add("Text", unit, "???");
}
It would need to be a public property of an instance; in this case, the "this" would suffice:
public int Unit {get;set;}
private void Form1_Load(object sender, EventArgs e)
{
textBox1.DataBindings.Add("Text", this, "Unit");
}
For two-way notification, you'll need either UnitChanged or INotifyPropertyChanged:
private int unit;
public event EventHandler UnitChanged; // or via the "Events" list
public int Unit {
get {return unit;}
set {
if(value!=unit) {
unit = value;
EventHandler handler = UnitChanged;
if(handler!=null) handler(this,EventArgs.Empty);
}
}
}
If you don't want it on the public API, you could wrap it in a hidden type somewhere:
class UnitWrapper {
public int Unit {get;set;}
}
private UnitWrapper unit = new UnitWrapper();
private void Form1_Load(object sender, EventArgs e)
{
textBox1.DataBindings.Add("Text", unit, "Unit");
}
For info, the "events list" stuff goes something like:
private static readonly object UnitChangedKey = new object();
public event EventHandler UnitChanged
{
add {Events.AddHandler(UnitChangedKey, value);}
remove {Events.AddHandler(UnitChangedKey, value);}
}
...
EventHandler handler = (EventHandler)Events[UnitChangedKey];
if (handler != null) handler(this, EventArgs.Empty);
You can use a binding source (see comment). The simplest change is:
public partial class Form1 : Form
{
public int Unit { get; set; }
BindingSource form1BindingSource;
private void Form1_Load (...)
{
form1BindingSource.DataSource = this;
textBox1.DataBindings.Add ("Text", form1BindingSource, "Unit");
}
}
However, you'll gain some conceptual clarity if you separate out the data a bit:
public partial class Form1 : Form
{
class MyData {
public int Unit { get; set; }
}
MyData form1Data;
BindingSource form1BindingSource;
private void Form1_Load (...)
{
form1BindingSource.DataSource = form1Data;
textBox1.DataBindings.Add ("Text", form1BindingSource, "Unit");
}
}
HTH. Note access modifiers omitted.
One of the things I like to do is to create "presentation" layer for the form. It is in this layer that I declare the properties that are bound to the controls on the form. In this case, the control is a text box.
In this example I have a form with a textbox to display an IP Address
We now create the binding source through the textbox properties. Select DataBindings->Text. Click the down arrow; select 'Add Project Data Source'.
This starts up that Data Source wizard. Select Object. Hit 'Next'.
Now select the class that has the property that will be bounded to the text box. In this example, I chose PNetworkOptions. Select Finish to end the wizard. The BindingSource will not be created.
The next step is to select the actual property from the bound class. From DataBindings->Text, select the downarrow and select the property name that will be bound to the textbox.
In the class that has your property, INotifyPropertyChanged must implemented for 2-way communication for IP Address field
public class PNetworkOptions : IBaseInterface, INotifyPropertyChanged
{
private string _IPAddress;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string IPAddress
{
get { return _IPAddress; }
set
{
if (value != null && value != _IPAddress)
{
_IPAddress = value;
NotifyPropertyChanged("IPAddress");
}
}
}
}
In the form constructor, we have to specifically define the binding
Binding IPAddressbinding = mskTxtIPAddress.DataBindings.Add("Text", _NetOptions, "IPAddress",true,DataSourceUpdateMode.OnPropertyChanged);

Categories

Resources