c# - 在WinForms之间传递连续数据

标签 c# .net winforms

我正在制作个人WinForms应用。在我的情况下,我有一个C#Form1Form1不断从Internet获取实时Exchange数据。现在,我单击Form1上的一个按钮,然后Form2打开。现在,我需要Form1Form2的一些值。

我在Form2上有一个计时器,可以从Form1收集数据,但是如何?

我曾尝试使用属性,但无法做到这一点,因为它在初始化Form2时仅更新一次。

有什么办法吗?

另外,如果没有同时创建类,则如何将类的单个实例传递给这两种形式?

最佳答案

I.解决方案:使用通用数据源

方法1:具有事件的数据源

好吧,如果是我,我可能不会尝试直接从Form1获取数据。相反,我将建立一个通用的数据源,然后您甚至可以消除Form2上的计时器,并根据需要在数据进入时驱动它。 (或者,您可以保留它,然后按照需要的时间间隔从数据源中拉出。)

就像这样:

数据源类

public class ExchangeCommonDataSource
{
    public event EventHandler NewDataReceived;
    public void FireNewDataReceieved()
    {
        if (NewDataReceived != null)
           NewDataReceived();
    }

    private string mySomeData1 = "";
    public string SomeData1 
    {
        get
        {
            return SomeData1;
        }

        set
        {
            SomeData1 = value;
            FireNewDataReceieved();
        }
    }

    // properties for any other data 
}


然后,当您打开表单时,只需创建一个ExchangeCommonDataSource实例,并将其传递给两个表单。在接收数据的表单中,您将要创建一个事件处理程序函数,无论将数据源传递给它的什么地方,都将连接该事件。

示例:接收类代码

public void HandleDataReceived(object sender, EventArgs e)
{
    // display the data
    DoSomethingWith(mySource.SomeData1);

    // etc...
}

private ExchangeCommonDataSource mySource;
public void SetDataSource(ExchangeCommonDataSource newSource)
{
    mySource = newSource;
    mySource.NewDataRecieved += new EventHandler(HandleDataReceived);
}


然后,在第一种形式中,您只需设置所需的属性。实际上,您可以通过单独的事件处理程序,或通过创建自己的派生EventArgs,然后使用EventHandler<ExchangeCommonEventArgs>而不是常规事件处理程序,来指定要加载的实际数据的通知。

示例:主窗体数据访问器

public void GetDataFromExchange()
{
    mySource.SomeData1 = GetSomeData1FromExchange();
}


同样,通过这种方式,您不仅可以让这两种形式进行交流;如果您决定将其拆分为不同的形式,则可以让每个模型都具有数据源的副本,并且每个模型都可以处理您定义的事件或新事件,并且您不必局限于模型期望彼此之间直接通信。例如,这还将允许创建一个单独的类,该类将一些日志数据写入磁盘或您可以想象的任何其他内容,而无需对现有的任何内容进行重大更改。



二。外部更新的可扩展性

调度员基础类

因此,如果您想更新以最终甚至发送到另一个应用程序或另一台计算机,该怎么办?

嗯,这实际上得到了很好的解释,因为您对表单没有任何依赖性。因此,假设您想支持三种方法:初始,表单到表单方法;通过命名管道发送到同一台计算机上的另一个应用程序;和TCP / IP完全连接到另一台计算机。您需要做的就是定义一个充当调度程序的类,将其作为接收器进行连接,然后可以将该对象连接起来以接收来自表单的事件并将数据放置在所需的位置。

定义一个抽象类或接口来做到这一点应该很简单,然后简单地为您要支持的任何模式派生一个类:

示例:概念抽象Dispatcher类

public class ExchangeDataDispatcher :
   IDisposable
{
    public ExchangeDataDispatcher(ExchangeCommonDataSource parDataSource)
    {
        myDataSource = parDataSource;
        myDataSource.HandleDataReceived += 
            new EventHandler(HandleDataReceived);

        DispatcherInitialization();
    }

    private ExchangeCommonDataSource myDataSource;

    private void HandleDataReceived(object sender, e EventArgs)
    {
        // here you could record statistics or whatever about the data
        DispatcherHandleDataReceived(EventArgs);
    }

    protected abstract void  DispatcherHandleDataReceived(e EventArgs);

    protected abstract void DispatcherShutdown();

    // significantly ripped from Microsoft's page on IDisposable
    private bool disposed = false;
    protected virtual void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if(!this.disposed)
        {
            // If disposing equals true, dispose all managed
            // and unmanaged resources.
            if(disposing)
            {
                // call a function which can be overridden in derived
                // classes
                DispatcherShutdown();
            }

            // Note disposing has been done.
            disposed = true;
        }
    }        
}


有关出色的示例代码和有关IDisposable的更多信息,请参见the Microsoft page on IDisposable

派遣其他通信方法的调度程序

没有办法使表单本身从此类中派生,但是没有真正的需要,因为您可以像以前一样进行连接。但是,作为一个简单的示例(只是名义上的,实际上并没有实际实现协议,您确实应该真正考虑实现这些类型的事物的最佳方法,但是我想为您提供一个相当全面的示例说明,它并不那么简单就像真正天真的版本一样。)

示例:(非常)基于概念的管道分派器

// add these to your using statments
using System.IO.Pipes;
using System.Threading;

// NOTE: take all the async stuff with a grain of salt; this should give you a
// basic idea but there's no way I've gotten it right without actually testing
// and debugging everything. See the link 
// http://stackoverflow.com/questions/6710444/named-pipes-server-read-timeout
// for some information on why it has to be done this way: basically timeout
// is not supported for named pipe server streams.
public class ExchangeDataLocalMachineDispatcher :
   ExchangeDataDispatcher
{
    // see http://www.switchonthecode.com/tutorials/dotnet-35-adds-named-pipes-support
    // for some info on named pipes in .NET
    public ExchangeDataLocalMachineDispatcher(
        ExchangeCommonDataSource parDataSource, 
        NamedPipeServerStream ServerPipe
    ) :
      base(parDataSource)
    {
        myPipe = ServerPipe;

        // do any extra initialization, etc. here, negotiation for instance

        StartPipeThread();
    }

    private NamedPipeServerStream myPipe;
    private ExchangeCommonDataSource myDataSource;

    // assuming you have PipeMessage defined and that your handler
    // fills them in.
    private List<PipeMessage> myOutgoingMessages = 
        new List<PipeMessage>(); 

    private Thread myPipeThread;
    private bool EndPipeListener = false;
    private AutoResetEvent myWaitEvent = null;
    private AutoResetEvent myDataReadyToGoEvent = null;

    // set this to something reasonable for the response timeout
    private int WaitTimeout = 10000; 

    // example: at least every minute there should be data to send
    private int WaitForDataToSendTimeout = 60000; 

    private void StartPipeThread()
    {
        IAsyncResult LastResult = null;

        Action<IAsyncResult> WaitForResult =
            (a) =>
            {
                LastResult = a;
                myWaitEvent.Set();
            }

        myPipeThread = new System.Threading.ThreadStart(
        () => 
        {
          try
          {
              myWaitEvent = new AutoResetEvent(false);

              myPipe.BeginWaitForConnection(
                  WaitForResult, null
              );

              bool TimedOut = !myWaitEvent.WaitOne(WaitTimeout);

              if (TimedOut || !LastResult.IsCompleted)
                  throw new Exception("Error: pipe operation error.");

              while (!EndPipeListener)
              {
                  byte[] Response = myPipe.BeginRead(
                     WaitForResult, null
                  );

                  myWaitEvent.WaitOne(WaitTimeout);

                  if (TimedOut || !LastResult.IsCompleted)
                      throw new Exception("Error: pipe operation error.");

                  // another assumed function to handle ACKs and such
                  HandleResponse(Response);

                  myWaitEvent.Set();

                  // now wait for data and send
                  bool TimedOut = 
                      myDataReadyToGoEvent.WaitOne(WaitForDataToSendTimeout);

                  if (TimedOut || !LastResult.IsCompleted)
                      throw new Exception("Error: no data to send.");

                  // an assumed function that will pull the messages out of
                  // the outgoing message list and send them via the pipe
                  SendOutgoingMessages();

                  myDataReadyToGoEvent.Set();
              }

              myWaitEvent.Set();
          }

          finally
          {
              // here you can clean up any resources, for instance you need
              // to dispose the wait events, you can leave the pipe for the
              // DispatcherShutdown method to fire in case something else
              // wants to handle the error and try again... this is all
              // fairly naive and should be thought through but I wanted  
              // to give you some tools you can use.

              // can't remember if you're supposed to use .Close
              // .Dispose or both off the top of my head; I think it's
              // one or the other.

              myWaitEvent.Dispose();
              myDataReady.Dispose();

              myWaitEvent = null;
              myDataReady = null;     
          }
        }
        );
    }

    protected PipeMessage[] ConstructEventMessage(e EventArgs)
    {
        // actually we're not using the event args here but I left it
        // as a placeholder for if were using the derived ones.

        return 
            PipeMessage.CreateMessagesFromData(
                myDataSource.GetMessageData()
            );
    }

    protected override void  DispatcherHandleDataReceived(e EventArgs)
    {
        // create a packet to send out; assuming that the 
        // ConstructEventMessage method is defined

        myOutgoingMessages.Add(ConstructEventMessage(e));
    }

    protected override void DispatcherShutdown()
    {
        // this is called from the base class in the Dispose() method
        // you can destroy any remaining resources here
        if (myWaitEvent != null)
        {
            myWaitEvent.Dispose();
        }

        // etc. and

        myPipe.Dispose();
    }

    // you could theoretically override this method too: if you do, be
    // sure to call base.Dispose(disposing) so that the base class can
    // clean up if resources are there to be disposed. 
    // protected virtual void Dispose(bool disposing)
    // {
    //     // do stuff
    //     base.Dispose(disposing);
    // }
}


ew请注意,我目前对StartPipeThread函数的长度非常不满意,我肯定会对其进行重构。

因此,您还可以为TCP / IP套接字或您可以想象的任何协议实现此功能,并且无需继续修改第一部分中的类就可以处理所有这些内容。

我对此处任何代码的质量表示歉意;我愿意就此提出建议/更正/批评,如果您让我知道,我会尽力进行更正。 :P



三,将数据放在需要的地方

设置完之后,您需要将相同的数据传递给正在使用的任何表单。如果您不同时创建两个表单,那么您将需要某种方式来获取每个目标对同一数据源的引用。 (注意:这些选项的编号决不意味着这些是您唯一的选择!)

以下是一些这样做的选项:

选项1:通过主表单上的属性

如果您的主窗体负责创建每个子窗体,例如通过菜单项,则此方法是合适的。您只需创建一个成员变量来保存数据,然后在任何创建数据的地方都将对它的引用存储在该成员中。如果您有多个来源实例,则可以存储它们,例如在一本字典中,可让您查找所需的字典。

示例:主表单代码

private ExchangeCommonDataSource myData { get; set; }

// you can also store in something that lets you identify multiple
// possible data sources; in this case, you could use, say, email address
    // as a lookup: myData["mickey@example.com"]; 

//private Dictionary<string, ExchangeCommonDataSource> myData =
//  new Dictionary<string, ExchangeCommonDataSource>();

public frmMyMainForm()
{
    InitializeComponent();

    // ... other initialization for the main form ...

    // create the data here and save it in a private member on your
    // form for later; this doesn't have to be in the constructor,
    // just make sure you save a reference to the source when you 
    // do create your first form that uses the source.
    myData = new ExchangeCommonDataSource();
}

// then, in the methods that actually create your form
// e.g. if creating from a menu item, the handlers
public void FirstFormCreatorMethod()
{
    frmFirstForm = new frmFirstForm(myData);
    frmFirstForm.MdiParent = this;
    frmFirstForm.Show();
}

public void SecondFormCreatorMethod()
{
    frmSecondForm = new frmSecondForm(myData);
    frmSecondForm.MdiParent = this;
    frmSecondForm.Show();
}


选项II:数据源上的static属性

如果从主表单外部创建表单,则可以使用此选项,在这种情况下,您将无法访问其方法。该方法背后的想法是,您想要一种简单的方法来查找所需的任何项,而与主窗体本身无关,并且通过提供静态方法,其他数据使用者可以使用只能访问以下内容的属性访问其自身的源:类声明,然后是某种键(如果可以有多个源)。

示例:ExchangeCommonDataSource.cs

// a dummy source class; this is just the parts that were relevant
// to this particular discussion.
public partial class ExchangeCommonDataSource
{
    public string Username { get; set; }
    public string OptionalString { get; set; }
    public int MailboxNumber { get; set; }
    public Guid SourceGuid { get; set; }
    public long BigNumber { get; set; }


    // these static members provide the functionality necessary to look
    // retrieve an existing source just through the class interface 

    // this holds the lookup of Guid -> Source for later retreival
    static Dictionary<Guid, ExchangeCommonDataSource> allSources = 
            new Dictionary<Guid,ExchangeCommonDataSource>();

    // this factory method looks up whether the source with the passed 
    // Guid already exists; if it does, it returns that, otherwise it
    // creates the data source and adds it to the lookup table
    public static ExchangeCommonDataSource GetConnection(
            Guid parSourceGuid, string parUsername, long parBigNumber
    )
    {
        // there are many issues involved with thread safety, I do not
        // guarantee that I got it right here, it's to show the idea. :)

        // here I'm just providing some thread safety; by placing a lock 
        // around the sources to prevent two separate calls to a factory
        // method from each creating a source with the same Guid. 
        lock (allSources)
        {
            ExchangeCommonDataSource RetVal;
            allSources.TryGetValue(parSourceGuid, out RetVal);

            if (RetVal == null)
            {
                // using member initializer, you can do this to limit the
                // number of constructors; here we only need the one 
                RetVal = new ExchangeCommonDataSource(parSourceGuid) {
                    Username = parUsername, BigNumber = parBigNumber
                };

                allSources.Add(parSourceGuid, RetVal);
            }

            return RetVal;
        }
    }

    // this function is actually extraneous since the GetConnection 
    // method will either create a new or return an existing source.
    // if you had need to throw an exception if GetConnection was
    // called on for existing source, you could use this to retrieve
    public static 
        ExchangeCommonDataSource LookupDatasource(Guid parSourceGuid)
    {
        // again locking the sources lookup for thread-safety. the 
        // rules: 1. don't provide external access to allSources
        //        2. everywhere you use allSources in the class, 
        //           place a lock(allsources { } block around it
        lock (allSources)
        {
            ExchangeCommonDataSource RetVal;
            allSources.TryGetValue(parSourceGuid, out RetVal);
            return RetVal;
        }
    }

    // private constructor; it is private so we can rely on the 
    // fact that we only provide factory method(s) that insert the
    // new items into the main dictionary
    private ExchangeCommonDataSource(Guid SourceGuid) 
    {
        // if you didn't want to use a factory, you could always do
        // something like the following without it; note you will
        // have to throw an error with this implementation because 
        // there's no way to recover. 

        //lock (allSources)
        //{
        //   ExchangeCommonDataSource Existing; 
        //   ExchangeCommonDataSource.allSources.
        //      TryGetValue(parSourceGuid, out Existing);

        //   if (Existing != null)
        //      throw new Exception("Requested duplicate source!");
        //}

        // ... initialize ...
    }
}


现在访问,客户端只需要具有某种密钥即可访问数据:

示例:frmClientClass.cs

public partial class frmClientClass 
{
    ExchangeCommonDataSource myDataSource = null;

    public void InitializeSource(Guid parSourceGuid)
    {
        myDataSource = ExchangeCommonDataSource.GetConnection(parSourceGuid);
    }
}


与选项1相比,我发现这通常是一个更具吸引力的解决方案,原因仅在于可以访问该类和ID的任何内容都可以获取数据源,并且因为它相当容易实现,并且为执行数据的多个实例提供了自动支持。源类。

它的开销相当低,而且由于在大多数情况下获取数据源是不应该在紧密循环中完成的(并且如果是这样,您将拥有本地副本,而不是每次都从字典中查找它们) )任何小小的性能损失都值得使用。而且,最重要的是,即使您从一个数据源开始,也可以轻松地将应用程序扩展到更多数据,而无需重写任何代码或进行任何进一步的工作。

例如,假设您只有一个数据源,一种非常快速的使用此方法的方法就是将已知值用于Dictionary键,然后您现在就可以在第二个中进行硬编码了。因此,例如,您可以将空的GUID作为键,然后将其用于两个表单。即主表单或您的第一个数据表单将使用Guid.Empty调用create方法以最初创建数据,然后在需要打开第二个表单时使用它来访问数据。

选项3:“单例”模式类

好的,我不会花太多时间或为此编写代码,但是如果我不提的话,我会很失落。它与选项2非常相似,不同之处在于,不是创建静态字典来查找多个数据源,而是创建一个类,该类具有存储在静态属性中的该类的一个实例,并且防止(通过异常)任何创建尝试更多课程。然后,将所有构造函数设置为private,如果静态变量已经包含一个对象,则使它们抛出异常,然后创建一个getInstance()方法,该方法返回该类的单个实例,如果该实例为null,则将其创建。

现在,在编写传统的单例时,您需要了解一些线程安全性的棘手问题,因此请务必理解这些问题(StackOverflow上存在一些与此问题有关的问题)。如果您不需要任何特殊知识来构造类的实例,则可以通过在声明它的位置简单地初始化变量来避免问题(例如static MyClass theInstance = new MyClass();,如果您曾经使用过,我强烈建议您这样做。

我在(相当遥远的)过去曾经使用过Singletons,这并不是说它们偶尔没有用处,尤其是在嵌入式系统中。但是,这不是嵌入式系统,几乎每次我在GUI应用程序中使用Singleton时,我都后悔这样做,因为最终我最终将其重写为允许多个实例的内容。如果您真的只需要一个副本,那么您要做的就是将一个成员变量放入使用它的类中,例如您的主表单,并确保您只创建一个。这样,您甚至可以通过在类中设置一个可以触发异常的静态标志来使用该模式;首次创建对象时将其设置为true,然后如果为true,则可以引发异常。

无论如何,我个人何时写单身人士的第一个规则是:除非确定您永远不需要一个,否则不要这样做。如果它通过了那条规则,那么第二条规则是:您错了,有一种可能发生的方法,因此只需将其编写为普通类并以其他方式处理它的单例性即可。 :)不过,认真的说,真正的规则是,除非您有充分的理由或这样做有很大的好处,否则请不要这样做。

重申一下:无需编写规范的单例类就可以完成单例模式。模式很好,只要以这种方式来实现,即需要该类的第二个实例时,消除该模式的成本就非常低。

选项4:单独的课程

选项4与选项2非常相似,但在第二类中实现。 (实际上,如果您认为自己可能有多个数据源,则值得从这里开始,尽管最初需要花更多的时间来进行设置。)而不是将静态项作为该类的成员,而是实现具有此类内容并提供访问权限的另一个类。这是一种将类本身与创建它分离的方式。例如,如果您正在编写一个库,并且想要提供几种不同类型的数据源,则可以实现一个基类,然后从该基类派生其他对象,然后通过提供工厂的类提供创建机制。创建不同种类的方法。

在这种情况下,您甚至可能根本不希望使用数据源的任何东西完全了解数据源类的实现,而只是通过基本接口,这提供了一种简单的方法那。如果必须将所有内容都写为基类的静态成员,那么每次派生新类时,都将强制对基进行重写,并且还迫使基层对派生类有所了解。通常是要避免的事情。换句话说,这并不是说它永远不会有用,而是要在没有充分理由的情况下不要这样做,也不要在不理解其含义的情况下这样做。

示例:外部类的代码

InfostoreBase.cs

// our data source base class; could do interface instead like:
// public interface IInfostoreBase
public abstract class InfostoreBase
{
    public abstract int Information { get; set; }
    public abstract string NameOfItem { get; set; }
    public abstract decimal Cost { get; set; }

    // ... etc ...
}


InfostoreEnterprise.cs

public class InfostoreHomeEdition :
    InfostoreBase
{
    public override int Information { get { /* ... */ } set { /* ... */ }}
    public override string NameOfItem { get { /* ... */ } set { /* ... */ }}
    public override decimal Cost { get { /* ... */ } set { /* ... */ }}

    public void SetFeatures(string parSomething) { /* ... */ }
}


InfostoreHomeEdition.cs

public class InfostoreEnterpriseEdition :
    InfostoreBase
{
    public override int Information { get { /* ... */ } set { /* ... */ }}
    public override string NameOfItem{ get { /* ... */ } set { /* ... */ }}
    public override decimal Cost { get { /* ... */ } set { /* ... */ }}

    public void SetBaseDiscount(decimal parSomethingElse) { /* ... */ }
}


InfostoreProvider.cs

public class InfostoreProvider
{
    static Dictionary<Guid, InfostoreBase> allSources = 
            new Dictionary<Guid,InfostoreBase>();

    public static InfostoreBase 
        GetHomeConnection(Guid CustomerKey, string HomeFeatures)
    {
        lock (allSources)
        {
            InfostoreBase RetVal;

            if (!ValidHomeKey(CustomerKey))
                throw new 
                    InvalidKeyException("not valid for Home Edition");

            allSources.TryGetValue(CustomerKey, out RetVal);

            if (RetVal == null)
            {
                RetVal = new InfostoreHomeEdition();
                allSources.Add(CustomerKey, RetVal);
            }

            var ActualVersion = (InfostoreHomeEdition) RetVal;

            RetVal.SetFeatures(HomeFeatures);

            return RetVal;
        }
    }

    public static InfostoreBase 
        GetEnterpriseConnection(Guid CustomerKey, decimal BaseDiscount)
    {
        lock (allSources)
        {
            InfostoreBase RetVal;

            if (!ValidEnterpriseKey(CustomerKey))
                throw new 
                    InvalidKeyException("not valid for Enterprise Edition");

            allSources.TryGetValue(CustomerKey, out RetVal);

            if (RetVal == null)
            {
                RetVal = new InfostoreHomeEdition();
                allSources.Add(CustomerKey, RetVal);
            }

            var ActualVersion = (InfostoreEnterpriseEdition) RetVal;

            RetVal.SetBaseDiscount(CostBase);

            return RetVal;
        }
    }
}


客户端类中的代码

private InfostoreBase myConnectionSource;
private void Initialize()
{
    // ...

    myConnectionSource = 
        InfostoreProvider.GetConnection(
            myKey, isEnterprise, myData
        );

    //...
}


闭幕

我认为这涵盖了很多可能的解决方案;它们都不是特别难实现的,并且每个都有其优点和缺点。通常,我会选择选项2或选项4,但是[破记录]它始终取决于您的实际情况。我认为使用扩展这些扩展来处理许多不同的情况将是相当容易的。当然,如果有任何问题,请告诉我。

关于c# - 在WinForms之间传递连续数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7142603/

相关文章:

c# - .NET RTD/COM Excel 互操作错误在一个用户的机器上?

c# - 获取数字的十进制变化

c# - 在 C# 中输入 ComboBox

c# - C# 代码中的模式匹配 F# 类型

c# - MVC : Component gets rendered, 中的 Blazor 但 @onclick 不工作。连接问题

c# - 找出差为 K 的数组中的对数?

c# - 如何在Windows窗体应用程序中获取应用程序物理路径

c# - LINQ-to-SQL .ExecuteCommand() 不适用于参数化对象名称

c# - 无法将泛型类型转换为接口(interface)

c# - 刷新表单后如何保留绘制的形状?