在 Windows 窗体之间共享数据






4.88/5 (62投票s)
在 Windows 应用程序中,将值从辅助窗体传递到主窗体或在多个应用程序之间共享数据(.NET 应用程序)。
引言
本文内容
- 共享数据的目的
- 如何做到?
阅读本文后,您将能够
- 将值从辅助窗体传递到主窗体(Windows 应用程序)
- 在多个应用程序之间共享数据(.NET Remoting)
上述目标都使用 C# 实现,并且无需存储数据!
为什么要共享?
有时会出现这样的情况:数据在一个 WinForm 中输入,但被其他窗体使用。我的意思是,在一个简单的情况下,您在 `Invoice` 窗体上单击一个按钮以打开 `SelectCustomerForm`,然后您选择一个客户并关闭该窗体,因此,所选客户将在主窗体中可用并可供使用。有时,情况并不像看起来那么简单。例如,当从另一个窗体的 `ListBox` 中选择一个名字时,需要显示一些详细信息。因此,找到一些在窗体之间共享数据的方法至关重要。
有哪些解决方案?
1. 关闭一个窗体后,需要访问已选择的对象
假设 `EnterNameForm` 上有一个 `TextBox`,用户可以通过它输入一个名字。您想调用一个窗体的 `ShowDialog()` 方法,然后选择一个名字。如果您关闭该窗体,如何访问该名字?
EnterNameForm enterNameForm = new EnterNameForm();
enterNameForm.ShowDialog();
我认为一个好的做法是为 `EnterNameForm` 定义一个属性
public string EnteredName { get; set; }
在 `FormClosed` 事件中,将该名字赋给 `EnteredName` 属性
private void EnterNameForm_FormClosed(object sender, FormClosedEventArgs e)
{
this.EnteredName = !string.IsNullOrEmpty(this.NameText.Text.Trim())?this.NameText.Text.Trim(): string.Empty;
}
关闭窗体后,公共属性仍然可用。
EnterNameForm enterNameForm = new EnterNameForm();
this.AddOwnedForm(enterNameForm);
enterNameForm.ShowDialog();
if (!string.IsNullOrEmpty(enterNameForm.EnteredName))
HelloLable.Text = string.Format("Hello, {0}", enterNameForm.EnteredName);
2. 需要在选定对象时立即访问该对象
假设一个名为 `PersonList` 的窗体上有一个名为 `Person` 的 `ListBox`。它通过 `Show()` 显示。您知道这与之前的情况(ShowDialog)不同。因为没有关闭窗体的机会,所以之前的解决方案不起作用!
一种解决方案是使用 `delegate`。它能够在处理事件后传递对象。
所以我建议在 `PersonList` 窗体中使用 `delegate`
public delegate void ItemChangedHandler(Person sender);
public ItemChangedHandler ItemChanged;
之后,选择对象,它就准备好使用了
private void PersonListBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (ItemChanged != null)
ItemChanged((Person)(((ListBox)sender).SelectedItem));
}
现在,在主窗体中
PersonList personList = new PersonList();
this.AddOwnedForm(personList);
personList.ItemChanged += new PersonList.ItemChangedHandler(ShowItem);
SetPersonListPosition(personList);
personList.Show();
`ShowItem` 是一个开发者定义的可以把对象放到主窗体中的方法
举个例子
private void ShowItem(Person person)
{
this.ChristianNameText.Text = person.ChristianName;
this.SurnameText.Text = person.Surname;
//...
}
总而言之,`delegate` 可以在数据更改时立即传递数据。
3. 需要集中处理来自多个窗体的一些数据
我确信一个简单的方法是使用 `static` 字段/属性。为什么不行?
如果我可以这么说的话,很明显,一些“反对静态”的挑剔者会出现并指责我,说
“停下,把手举起来让我看见。静态?嗯?!嗯?!...” 还有等等等等!
事实上,开发人员并非必须偶尔避免使用静态字段/属性,尤其是在“Windows 应用程序”中。例如,我可以提醒您“Singleton 模式”及其公共静态属性。反对者认为“它在使用多线程时可能引起问题,因为它不是线程安全的”。好的,同意,但是首先,您会在每一个地方、每一次都使用“Thread
”吗?(我认为不会)。其次,有各种方法可以解决这个问题。例如,看看这些链接:这个 和 这个。最重要的是,要极其小心地在多个线程中使用静态属性!
让我们回到正题
如果我们为所有窗体需要一些计数器,我们该怎么办?静态 `Dictionary` 不是一个绝佳的解决方案吗?
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;
}
}
并且它可以在每个窗体的 `Load` 事件中准备好工作
private void MyForm_Load(object sender, EventArgs e)
{
Data.AddToCounter(this.Name);
CounterLable.Text = Data.Counter[this.Name].ToString();
}
有关更多信息,请查看源代码文件。
4. 需要在多个应用程序之间共享数据
在单个项目中,在两个窗体之间传递数据并不难,但对于多个应用程序呢?让我们来看一个场景
在一个会计应用程序中定义了一个客户(John Smith),然后将使用另一个应用程序向他发送短信。如何在这两个独立的项目中共享客户?
有各种解决方案可用,例如:连接到单个数据库、套接字编程、SOA、MemoryMappedFile 等等。
我想继续讨论“.Net Remoting”主题和“如何将数据从服务器传递到客户端?”。这是一个通用的、简单的方法,并非 Windows 应用程序专用。
假设我们在一个 Windows 应用程序中有一个 `Person` 的实例,它将传递给另一个控制台应用程序
第一个必要步骤是引用 `System.Runtime.Remoting`,以及创建一个用于 Remoting 的类并继承自 `MarshalByRefObject`。这是一件重要的事情。我创建了一个使用“类库解决方案”的示例类
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;
}
`delegate` 使此类(类的实例)能够充当服务器和客户端之间传递数据的中间人。它有两个参数,`clientID` 和 `result`。`clientID` 从客户端传递,以防止服务器广播,而 `result` 从服务器传递给特定客户端。(在此程序中,客户端的 ID 为 1。)
我们假设 Windows 应用程序是服务器。在窗体的构造函数中,使用此代码
channel = new TcpChannel(8080);
//Registering remote objects...;
RemotingConfiguration.ApplicationName = "Processor";
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject),
"MyURI",
WellKnownObjectMode.Singleton);
`8080` 是端口号,您可以选择其他号码!
我个人认为在窗体上放置一个 `CheckBox` 来共享或取消共享数据。
private void ShareCheckBox_CheckedChanged(object sender, EventArgs e)
{
if (!this.ShareCheckBox.Checked)
{
//Closing connections...;
ChannelServices.UnregisterChannel(channel);
RemotingObject.DataSent -= new RemotingObject.SendDataHandler(Method);
return;
}
RemotingObject.DataSent += new RemotingObject.SendDataHandler(Method);
//Registering channel...
ChannelServices.RegisterChannel(channel, false);
}
以及一个处理 `DataSent` 事件的方法。
public void Method(long clientID, ref Person person)
{
if (clientID != 1)
return;
person = selectedPerson != null ? selectedPerson : new Person();
// To prevent exception at client.
}
好的,我们来看服务器。
如果您采纳我的建议,为了测试该方法,之后创建一个控制台应用程序(客户端);请注意以下代码
static void Main(string[] args)
{
long clientId = 1; //You can change it or create a dynamic one for it.
Console.WriteLine("Starting Client {0}", clientId);
RemotingConfiguration.RegisterWellKnownClientType(typeof(RemotingObject),
"tcp://: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"://Quit the program
return;
case "N":
try // tring to get person from the server.
{
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)
// For example: If you run the client before the server.
{
Console.WriteLine(ex.Message);
}
Console.WriteLine();
break;
default:
//Send a message to server
instance.Send(clientId, prompt);
break;
}
}
}
创建 `RemotingObject` 的实例并调用 `Process` 方法时,服务器上会发生一个事件。方法的返回值是我们想要的共享对象。
Web 应用程序被迫请求响应,但其他应用程序不必加班工作。也许客户端通过循环请求不是个好主意!目标是演示如何共享数据。但是,如果您不想使用循环,可以使用 `WellKnownObjectMode.SingleCall` 而不是 `WellKnownObjectMode.Singleton`。
简而言之,有多种多样的计划可以共享应用程序之间的数据;我的目的是编写一本关于“使用 .NET Remoting 传递数据”的简单手册。就我个人而言,我还没有看到任何简单的文章。直到现在!
有没有办法从客户端向服务器发送消息?
答案是可以。
可以说,这需要更多的代码,也许还需要一个 `Control`。
我更喜欢使用 `ListBox` 在服务器上显示消息。我还计划将代码添加到 `RemotingObject` 类中
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;
客户端可以使用此代码发送消息
instance.Send(clientId, prompt);
然后在服务器中
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)//Invokerequred is because of different threads
{
MessageList.Invoke(new MethodInvoker(
delegate { MessageList.Items.Add(
string.Format("Client {0}: {1}", clientID, myMessage)); }));
}
}
现在,用户可以从客户端向服务器发送消息。就像输入消息然后按 Enter 键发送一样简单。
用户还可以从客户端使用这些命令
Q:退出程序。N:获取选定的姓名。
因此
嗯,已经提供了几种方法来允许我们在窗体或应用程序之间共享和传递数据。其中一些方法效果最好。例如:WCF(Windows Communication Foundation),正如微软所说,它提供了一个统一的编程模型,可以快速构建跨 Web 和企业进行通信的面向服务的应用程序。当然,我们应该寻求最好的,我一定程度上承认这一点,但其他解决方案没有理由被拒绝。
也许您愿意下载并查看源代码。我还要请您运行演示并发送您的建议来改进本文。
历史
好的,Collin Jasnoch 有一个想法,在解决方案 2(需要立即访问选定对象)中,可以用 INotifyPropertyChanged
代替 `delegate`。他是对的,我认为这是一个更好的(标准)主意。顺便说一句,我还有另一篇文章,其中使用了 INotifyPropertyChanged
。是关于“使用 C# 的 Windows 窗体应用程序的 MVVM(Model-View-ViewModel)模式”,而 INotifyPropertyChanged
接口是该模式中的主要部分。