2

まず、お詫び: このサイトに質問を投稿するのは初めてなので、書式設定や情報の誤りについてお詫び申し上げます。フォームにドロップされたシリアル ポートからデータを取得し、それを使用してテキスト ボックスやグラフにデータを入力することについて、多くの回答を見てきました。 、など、シリアルポートが別のスレッドで実行されているため、「Invoke」を使用してメインフォームで。

私は、私たちが常にクラスで使用するいくつかのコミュニケーションを「一般化」しようとしています (そうです、古い VB6 プログラマーは成長しようとしています :-) が、問題が発生しています。メインの program.cs でフォーム名を強制し、クラスに同じ名前空間を使用すると、いくつかのことができますが、これは目的に反します。また、クラスのシリアルポートの「受信」にもイベントを追加して、メインフォームでイベントを発生させようとしました。イベントが発生しようとしますが、クロス スレッド例外が発生します。

この時点でのコードは非常に大きいので、「アウトライン化」してみます。簡単に言えば、textbox1 というテキスト ボックスと "SerialThing" というクラスを含む "Form1" という for があるとします。


フォーム1:

SerialThing mySerialThing ;

Form1_Load:

mySerialThing = new SerialThing();

表示データ()

Textbox1.Text = "You Got Data!";

シリアルシング:

Static SerialPort myDevice;

初期化()

myDevice = new SerialPort;
myDevice.DataReceived += new SerialDataReceivedEventHandler(devicePort_DataReceived);

devicePort_DataReceived()

this.Invoke(new EventHandler(DisplayData));

上記は、シリアル ポートがメイン フォームに配置されている場合は機能しますが、クラス内に作成されている場合は機能しません。

繰り返しますが、複雑すぎたり、単純すぎたりしたら申し訳ありません。これを行うための「簡単な」方法を探していますが、クラスを「一般化」したままにします (理想的には、ワークスペース名を一致させる必要はありません)。

-ヴィン

4

1 に答える 1

1

これを行う方法はたくさんあります。カスタム イベント、デリゲート、および Invoke() を使用する従来のアプローチを紹介します。そのプロセスを理解することが重要だと思います。これを理解したら、いくつかの新しいアプローチにジャンプできます。

まず、SerialThing() クラスで、受信時にデータを渡すカスタム イベントを宣言します。

class SerialThing
{

    public delegate void DataReceivedDelegate(string data);
    public event DataReceivedDelegate DataReceived;

    static SerialPort myDevice;

    public SerialThing()
    {
        myDevice = new SerialPort();
        myDevice.DataReceived += new SerialDataReceivedEventHandler(myDevice_DataReceived);
    }

    void myDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        // ... grab the data and place into a string called "data" ...
        string data = "";

        // raise our custom event:
        if (DataReceived != null)
        {
            DataReceived(data);
        }
    }

}

次に、Form1 で、SerialThing のインスタンスを作成するときにそのカスタム イベントをサブスクライブします。さらに、そのイベントが受信されると、InvokeRequired、Invoke、およびデリゲートを使用して、セカンダリ スレッドからメイン スレッドへの呼び出しをマーシャリングします。

public partial class Form1 : Form
{

    SerialThing mySerialThing;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        mySerialThing = new SerialThing();
        mySerialThing.DataReceived += new SerialThing.DataReceivedDelegate(mySerialThing_DataReceived);
    }

    private delegate void DataReceivedDelegate(string data);

    void mySerialThing_DataReceived(string data)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new DataReceivedDelegate(mySerialThing_DataReceived), new Object[] { data });
        }
        else
        {
            textBox1.Text = data;
        }
    }

}

編集:以下のコメントに応えて...

デリゲートは、単に「メソッドへのポインター」と考えてください。デリゲートを実行すると、関連するメソッドが実行されます。

InvokeRequired() 部分は、コントロールを作成したスレッドとは異なるスレッドでコードが実行されているかどうかを判断します。この場合、コントロールはフォーム自体です ( this)。true が返された場合、イベントは別のスレッドで受信されました。次に、ブロックthis.Invoke()の真の部分の内側に線を引きます。If再びthisフォームを参照します。したがって、フォームは、それを作成したスレッド (メイン UI スレッド) で渡されたデリゲートの呼び出し (「実行」) を要求しています。再帰呼び出しの結果、既に存在しているのと同じメソッドを実際に指すデリゲートのインスタンスを作成します。2 番目のパラメーターは、デリゲートと共にパラメーターを渡すために使用される Object の配列です。

Invoke() が実行されると、再帰呼び出しのためにメソッドに再度入ることになります。ただし、この時点では、メイン UI スレッドで実行しているため、InvokeRequired() チェックは false を返します。Ifしたがって、TextBox を更新するステートメントの false 部分にドロップダウンします。このパターンでは、ステートメントのelseブロックでGUI コントロールを更新しても安全です。If

ここでは再帰呼び出しは必要ないことに注意してください。これは単にスタイルの選択です。代わりに、デリゲートが指す 2 番目の「ヘルパー」関数を使用して、代わりにそれを呼び出すこともできました。再帰的なアプローチにより、必要なメソッドの数が減ります。

これはおそらく、この種の問題を解決するための最も詳細なアプローチです。ただし、イベントとデータの流れ、およびスレッド間の移動を示しているので、気に入っています。

匿名デリゲートを使用して、すべてのフォーム コードをこれだけに短縮できます。

    private void Form1_Load(object sender, EventArgs e)
    {
        mySerialThing = new SerialThing();
        mySerialThing.DataReceived += delegate (string data)
        {
            this.Invoke((MethodInvoker)(delegate() { textBox1.Text = data; }));
        };
    }

私はあなたのことを知りませんが、元 VB6 プログラマーとして、最初にそのようなものを見たときは奇妙に見えます。

また、さまざまなスレッドで実行されていることがわかっているコンポーネントを使用しましたが、「フォームコード」はデリゲートのものを使用する必要がなかったため、クラスに埋め込むことができるものがあるのでしょうか?

はい、何らかの「魔法」をクラスに焼き付けて、既にメイン UI スレッドでイベントを発生させ、Invoke() 呼び出しを必要としないようにすることは可能です。これを行う 1 つの方法は、SynchronizationContextを使用することです。

このタイプの問題にアプローチする別の可能性は、メイン UI スレッドで発生する ProgressChanged() や RunWorkerCompleted() などのイベントを持つ BackgroundWorker() コントロールを使用することです (これらは内部で必要な呼び出しタイプのものを実行します)。あなたのために)。

于 2013-11-12T00:10:53.477 に答える