C# 在WinForms之间传递连续数据

C# 在WinForms之间传递连续数据,c#,.net,winforms,C#,.net,Winforms,我正在制作一个个人WinForms应用程序。在我的场景中,假设我有一个C#表单1Form1不断从互联网上获取实时交换数据。现在我点击Form1上的一个按钮,Form2打开。现在,我想从Form2上的Form1中获取一些值 我在Form2上有一个计时器,它可以从Form1收集数据,但是如何收集呢 我曾尝试使用属性,但无法做到这一点,因为它只在初始化Form2时更新一次 有解决办法吗 另外,如果不同时创建两个窗体,如何将一个类的单个实例传递给这两个窗体?编辑Form2的构造函数,以便在使用.Show

我正在制作一个个人WinForms应用程序。在我的场景中,假设我有一个C#
表单1
Form1
不断从互联网上获取实时交换数据。现在我点击
Form1
上的一个按钮,
Form2
打开。现在,我想从
Form2
上的
Form1
中获取一些值

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

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

有解决办法吗


另外,如果不同时创建两个窗体,如何将一个类的单个实例传递给这两个窗体?

编辑Form2的构造函数,以便在使用.Show或.ShowDialog运行新Form2时,可以从Form1传递一些值

Form2 myForm=新的Form2(value1、value2、value3

在Form2.cs上,您应将公共Form2()转换(或添加一个新的)为公共Form2(var值1,var值2…)

如果必须连续向Form2发送数据,可以使用共享内存或共享数据文件

I.解决方案:使用公共数据源 方法1:包含事件的数据源 如果是我,我可能不会直接从表格1中获取数据。相反,我将设置一个公共数据源,然后您甚至可以消除Form2上的计时器,并在数据进入时驱动它(如果您愿意)。(或者,您可以离开它,按照所需的时间间隔从数据源中提取数据。)

应该是这样的:

数据源类 然后,在打开表单时,只需创建ExchangeCommonDataSource的一个实例,并将其传递给这两个表单。在接收数据的表单中,您需要创建一个事件处理函数,并且无论您将其传递到何处的数据源,都将连接该事件

示例:接收类代码 然后,在第一个表单中,您只需设置所需的属性。实际上,您可以通过单独的事件处理程序,或通过创建自己的派生事件参数,然后使用
EventHandler
而不是常规事件处理程序,来获得指定要加载的实际数据的通知

示例:主窗体数据访问器 而且,通过这种方式,你不仅仅局限于这两种形式的交流;如果您决定使用不同的表单将其拆分,那么您可以让每个表单都有一个数据源的副本,并且每个表单都可以处理您定义的事件或新事件,并且您不需要绑定到期望彼此直接通信的模型。例如,这还允许创建一个单独的类,将一些日志数据写入磁盘,或者您可以想象的任何其他内容,而无需对任何现有内容进行重大更改


二,。外部更新的可扩展性 Dispatcher基类 所以,如果您希望更新,最终发送到另一个应用程序或另一台机器,甚至是其他应用程序,该怎么办

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

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

示例:一个概念化的抽象分派器类 有关IDisposable的一些重要示例代码和更多信息,请参阅

为其他通信方法导出调度员 没有办法使表单本身从这个类派生,但是没有真正的需要,因为您可以像以前一样连接。但是,作为一个很快的例子(只是名义上的,实际上并没有实现协议,你真的应该考虑实现这些类型的最佳方法,但我想给你一个相当全面的例子,它不是真正的天真的版本往往是简单的。) 示例:(非常)概念上的基于管道的调度器 选项二:
静态
数据源上的属性 如果表单是从主表单外部创建的,则可以使用此选项,在这种情况下,您将无法访问其方法。这种方法背后的思想是,您需要一种简单的方法来找到您需要的任何项目,独立于主窗体本身,并通过提供静态方法,其他数据使用者可以使用仅通过访问类声明和某种键(如果可以有多个源)即可访问的属性自己查找源

示例:ExchangeCommonDataSource.cs 我发现这是一个比选项1更具吸引力的解决方案,这仅仅是因为任何可以访问类和ID的东西都可以获得数据源,而且它非常容易实现,并且自动支持执行数据源类的多个实例

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

例如,假设您只有一个数据源,那么使用它的一个非常快速的方法就是只使用字典键的一个已知值,然后您现在就可以在第二个数据源中硬编码它。因此,例如,您可以将空GUID作为键,并将其用于两个表单。
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 
}
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);
}
public void GetDataFromExchange()
{
    mySource.SomeData1 = GetSomeData1FromExchange();
}
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;
        }
    }        
}
// 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);
    // }
}
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();
}
// 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 ...
    }
}
public partial class frmClientClass 
{
    ExchangeCommonDataSource myDataSource = null;

    public void InitializeSource(Guid parSourceGuid)
    {
        myDataSource = ExchangeCommonDataSource.GetConnection(parSourceGuid);
    }
}
// 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 ...
}
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) { /* ... */ }
}
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) { /* ... */ }
}
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
        );

    //...
}
public class FirstDataKindEventArgs : EventArgs
{
    public FirstDataKindEventArgs(int parID, string parName, string parOtherInfo)
    {
        Id = parId; 
        Name = parName;
        OtherInfo = parOtherInfo;
    }

    public int ID { get; set; }
    public string Name { get; set; }
    public string OtherInfo { get; set; }
} 

// plus other event arg definitions

public interface IExchangeDataProvider
{
    event EventHandler<FirstDataKindEventArgs> FirstDataKindReceived;
    event EventHandler<SecondDataKindEventArgs> SecondDataKindReceived;
    event EventHandler<ThirdDataKindEventArgs> ThirdDataKindReceived;       
}

public interface IExchangeDataReceiver
{
    void ConnectDataProvider(IExchangeDataProvider Provider);
}
public partial class MyProvidingForm : System.Windows.Forms.Form, IExchangeDataProvider
{
   // normal form stuff

   // ...

    #region IExchangeDataProvider
    public event EventHandler<FirstDataKindEventArgs> FirstDataKindReceived;
    public event EventHandler<SecondDataKindEventArgs> SecondDataKindReceived;
    public event EventHandler<ThirdDataKindEventArgs> ThirdDataKindReceived;

    public void FireDataReceived(EventArgs Data)
    {
        FirstDataKindEventArgs FirstKindData = Data as FirstDataKindEventArgs;

        if (FirstDataKindEventArgs != null)
            if (FirstDataKindReceived != null)
                FirstDataKindReceived(FirstKindData);

        //... etc. 
    }

    public void GotSomeDataOfTheFirstKind(int TheID, string SomeName, string Other)
    {
        FirstDataKindEventArgs eArgs = 
            new FirstDataKindEventArgs(TheId, SomeName, Other);

        FireDataReceived(eArgs);
    }
    public partial class FirstDataKindReceivingForm :
        System.Windows.Forms.Form,
        IExchangeDataReceiver
    {
        // usual form stuff

        // ...

        private IExchangeDataProvider myDataProvider;
        public void ConnectDataProvider(IExchangeDataProvider Provider)
        {
            myDataProvider = Provider;
            myDataProvider.FirstDataKindReceived += 
                new EventHandler<FirstDataKindEventArgs>(
                    HandleFirstKindOfDataReceived
                );
        }


        private void HandleFirstKindOfDataRecieved (
            object sender, FirstDataKindEventArgs
        )
        {
            // do whatever with data
        }
    }

    #endregion
}