Introduction
In this article:
- Reason for sharing data
- How to do it?
After this article, you will be able to:
- Pass values from a secondary form to the main form (Windows application)
- Share data among several applications (.NET Remoting)
The goals mentioned above are in C#, without storing data!
Why share?
It sometimes comes up that data is entered from a WinForm and it's being used by other forms. What I mean
is, in a simple position, you click a button on an Invoice
form to
open SelectCustomerForm
, and then you will select a customer and
close the form, therefore, the selected customer will be available and ready to use in the main form. And sometimes,
it's not as simple as all that.
For example, there is a need for showing some details, as soon as a name is
selected from a ListBox
of another form. So it is integral to have some solutions for sharing data amongst forms.
What are the solutions?
1. After closing a form, there is a need for accessing a selected object
Given there is a TextBox
on EnterNameForm
, a user
will be able to enter a name by using it. You want to call the ShowDialog()
method of a form then select a name. If you close the form, how can you access the name?
EnterNameForm enterNameForm = new EnterNameForm();
enterNameForm.ShowDialog();
One of the best courses of action I think would be to define a Property behind
EnterNameForm
:
public string EnteredName { get; set; }
In the FormClosed
event, assign the name to the EnteredName
property:
private void EnterNameForm_FormClosed(object sender, FormClosedEventArgs e)
{
this.EnteredName = !string.IsNullOrEmpty(this.NameText.Text.Trim())?this.NameText.Text.Trim(): string.Empty;
}
After we close the form, the public properties are still available.
EnterNameForm enterNameForm = new EnterNameForm();
this.AddOwnedForm(enterNameForm);
enterNameForm.ShowDialog();
if (!string.IsNullOrEmpty(enterNameForm.EnteredName))
HelloLable.Text = string.Format("Hello, {0}", enterNameForm.EnteredName);
2. There is the need for accessing a selected object as soon as it's selected:
Suppose there is a ListBox
called Person
on a form
PersonList
.
It's shown by Show()
. You know it is different from the previous situation (ShowDialog). Because there is not any grace to closing
the form and the previous solution doesn't work!
One solution using a delegate
. It'll be able to pass the object after handling the event.
So my advice would be to use the delegate behind the PersonList
form:
public delegate void ItemChangedHandler(Person sender);
public ItemChangedHandler ItemChanged;
And after that, select the object, and it's ready to use:
private void PersonListBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (ItemChanged != null)
ItemChanged((Person)(((ListBox)sender).SelectedItem));
}
Now, behind the main form:
PersonList personList = new PersonList();
this.AddOwnedForm(personList);
personList.ItemChanged += new PersonList.ItemChangedHandler(ShowItem);
SetPersonListPosition(personList);
personList.Show();
ShowItem
is a developer defined method that is able to put the object in the main form:
To exemplify what I mean, let's take this:
private void ShowItem(Person person)
{
this.ChristianNameText.Text = person.ChristianName;
this.SurnameText.Text = person.Surname;
}
To summarize, a delegate
is capable of passing data, as soon as data changes.
3. There is the need for centralizing some data from several forms:
I'm convinced that a simple approach is static
fields/properties. Why not?
If I may say so, it's clear that a few "opposed to static" fastidious will show up and flame me
saying that:
"Freeze, put yer hands up where I can see 'em. Static? Huh?! Huh?! ..." And blah blah blah !
As a matter of fact, developers don't have to avoid static fields/properties occasionally, especially in
a "Windows Application".
For instance I can remind you of the "Singleton Pattern" and its public static property. Opponents believe "It can introduce problems when using multithreading,
because it is not thread-safe". OK, granted, but first, are you going to use "Thread
", every where, every time? (I don't think so).
Secondly, there are various approaches to solving this problem. For example, look at these links: this and this.
The important thing is use a static property in multiple threads, with utmost care!
Let's return to our muttons:
What could we do if we need some counters for all forms? Isn't a static Dictionary
a mint solution?
public class Data
{
public static Dictionary<string, int> Counter = new Dictionary<long, string>();
public static void AddToCounter(string name)
{
int addNumber = 1;
if (Counter.Keys.Contains(name))
addNumber += Counter[name];
Counter[name] = addNumber;
}
}
And it's ready to work in the Load
event of every form:
private void MyForm_Load(object sender, EventArgs e)
{
Data.AddToCounter(this.Name);
CounterLable.Text = Data.Counter[this.Name].ToString();
}
For more information, look at the source code file.
4. There is a need for sharing data among several applications
Passing data between two forms is not so hard in a single project, but what about several applications? Let's look at
this scenario:
There is a defined customer (John Smith) at an accounting application, and then it's going to send him an SMS by using
another application. How can the customer be shared between two separate projects?
Various solutions are available, for example: Connecting to a single database, socket programming, SOA,
MemoryMappedFile, and the like.
I like to proceed to the ".Net Remoting" subject and "How to pass data from
a server to client?" It's a generic and simple approach,
not specific for a Windows application.
Suppose we have an instance of Person
in a Windows App that is going to pass to another Console application:
The first necessary step is a reference to System.Runtime.Remoting
, besides creating a class for
Remoting and it inheriting from MarshalByRefObject
. This is an important thing to do. I created an example class by using a "Class Library Solution":
public class RemotingObject: MarshalByRefObject
{
public Person MyPerson { get; set; }
public Person Process(long clientID)
{
Person result = new Person();
if (DataSent != null)
DataSent(clientID, ref result);
return result;
}
public delegate void SendDataHandler(long clientID, ref Person result);
public static SendDataHandler DataSent;
}
The delegate
enables this class (an instance of the class) to act like an intermediary in passing data, between
the server and client. It has two parameters, clientID
and result
.
clientID
is passed from clients
to prevent the server from broadcasting and result
is passed from the server to the specific client. (The client
has its ID = 1 in this program.)
Our Windows app is assumed as a server. In the constructor of the form, use this code:
channel = new TcpChannel(8080);
RemotingConfiguration.ApplicationName = "Processor";
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject),
"MyURI",
WellKnownObjectMode.Singleton);
8080
is the port number, you can choose something else!
Personally, I consider putting a CheckBox
on the form for sharing or unsharing data.
private void ShareCheckBox_CheckedChanged(object sender, EventArgs e)
{
if (!this.ShareCheckBox.Checked)
{
ChannelServices.UnregisterChannel(channel);
RemotingObject.DataSent -= new RemotingObject.SendDataHandler(Method);
return;
}
RemotingObject.DataSent += new RemotingObject.SendDataHandler(Method);
ChannelServices.RegisterChannel(channel, false);
}
And a method for handling the DataSent
event.
public void Method(long clientID, ref Person person)
{
if (clientID != 1)
return;
person = selectedPerson != null ? selectedPerson : new Person();
}
OK, let's leave the server.
If you follow my advice, for testing the approach, afterwards create a Console application (client); pay attention to the following code:
static void Main(string[] args)
{
long clientId = 1;
Console.WriteLine("Starting Client {0}", clientId);
RemotingConfiguration.RegisterWellKnownClientType(typeof(RemotingObject),
"tcp://localhost:8080/Processor/MyURI");
RemotingObject instance = new RemotingObject();
long bufferedID = 0;
string prompt;
while (true)
{
Console.Write("Prompt: ");
prompt = Console.ReadLine().Trim().ToUpper();
switch (prompt)
{
case "Q": return;
case "N":
try
{
Person result = instance.Process(clientId);
if (result != null && result.ID != bufferedID)
Console.WriteLine("{0} {1}", result.ChristianName, result.Surname);
bufferedID = result.ID;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine();
break;
default:
instance.Send(clientId, prompt);
break;
}
}
}
On creating an instance of RemotingObject
and calling the Process
method, an event occurs at
the server.
The result of the method is a desired shared object.
Web applications are compelled to request for responses, but the rest of the applications doesn't have to
be overtime work. Perhaps it's not a good idea that clients request by a loop!
The goal was demonstrating how to share data. But you may use WellKnownObjectMode.SingleCall
instead of WellKnownObjectMode.Singleton
, if you don't want a loop.
Briefly, there are very variegated plans to share data among applications; my intention is to compile a simple pamphlet on "Passing Data by using .NET Remoting".
In my own case, I haven't seen any simple articles for it. Yet!
Is there a way to send a message back to the server from the client?
The answer is yes.
Suffice it to say that, it needs more code and maybe a Control
.
I prefer a ListBox
to show messages in the server. I also plan to add the code to the RemotingObject
class:
public void Send(long clientID, string message)
{
if(SendMessage != null)
SendMessage(clientID, message);
}
public delegate void SendDataHandler(long clientID, ref Person result);
public static SendDataHandler DataSent;
The client can send a message by using this code:
instance.Send(clientId, prompt);
And in the server:
private void ShareCheckBox_CheckedChanged(object sender, EventArgs e){
RemotingObject.SendMessage +=
new RemotingObject.SendMessageHandler(MessageRecievedMethod);
}
private void MessageRecievedMethod(long clientID, string message)
{
string myMessage = message;
if (MessageList.InvokeRequired) {
MessageList.Invoke(new MethodInvoker(
delegate { MessageList.Items.Add(
string.Format("Client {0}: {1}", clientID, myMessage)); }));
}
}
Now, the user is able to send a message from the client to the server. As easy as typing a message and pressing
the Enter key to send.
The user can also use these commands from the client:
Q: Quit the program. N: Get the selected name.
Consequently
Well, several methods have been provided for allowing us to share and pass data among forms or applications. And some of them work the smartest.
For example: WCF (Windows Communication Foundation), as Microsoft says, provides a unified programming model for rapidly building service-oriented applications
that communicate across the web and the enterprise. Of course, we should be looking for the best, I granted it in a sense, but there is no reason why other solutions should be rejected.
Perhaps you'd care to download and look at the source code. I also insist you run
the demo and send me your suggestions to improve the article.
History
OK, Collin Jasnoch had an idea about implementing
INotifyPropertyChanged
instead of delegate
in solution 2 (need for accessing the selected object as soon as it's selected).
He's right and I think that's a better (standard) idea. By the way I have another article where I used
INotifyPropertyChanged
.
It's about "MVVM (Model-View-ViewModel)
Pattern For Windows Form Applications, using C#" and the
INotifyPropertyChanged
interface is the main thing in that pattern.