-1

GUI要素を更新するクラスがあります

public class UpdateLabelClass
{
    static MainGUI theForm = (MainGUI)Application.OpenForms[0];
    Label lblCurProgress = theForm.curProgress;

    public ProgressBarUpdate()
    {

    }
    public void UpdateLabel(String newLabel)
    {
        lblCurProgress.Text = newLabel;
    }
}

他のクラスでは、クラスのインスタンスを作成し、UpdateLabel(someString); を呼び出します。

問題は、ラベルの更新操作を飛ばしてしまうので「もしかしたらコードまで届いていないのかも」と思ったので、その直後に MessageBox.Show() を入れて、ラベルを更新しました。

ラベルの更新をスキップする原因として考えられるものは何ですか? プログラムは速くなりますか?

4

2 に答える 2

2

ほとんどの場合、メイン UI スレッドで長時間の操作を不適切に実行しているため、ラベルの更新が妨げられています。DoEvents() を呼び出すことで、これを「修正」できます。

public void UpdateLabel(String newLabel)
{
    lblCurProgress.Text = newLabel;
    Application.DoEvents();
}

しかし、これは悪い設計に付けられた応急処置にすぎません。 そのコードをバックグラウンド スレッドに適切に移動し、delegate/Invoke() を使用してラベルを更新する必要があります。

編集:(フォローアップの質問に答える)

デフォルトでは、アプリケーションは単一のスレッドで実行されます。これには、イベントを制御するために追加するコードだけでなく、アプリケーションを期待どおりに応答させるためにバックグラウンドで実行されている目に見えないコードも含まれます。ユーザー インタラクション (マウス クリック、キーボード プレスなど) や描画メッセージ (コントロールが変更されるとウィンドウが隠される) などは、キューに入れられます。キュー内の保留中のメッセージは、コードの実行が停止した後にのみ処理されます。長いループのように長いコードのチャンクを実行している場合、それらのメッセージは処理されるのを待っているキューに留まります。したがって、ラベルの更新は、ループが完了するまで発生しません。DoEvents() が行うことは、キュー内の保留中のメッセージを今すぐ処理するようにアプリケーションに指示することです。そして、現在実行中のコードに戻ります。これにより、期待どおりにラベルをリアルタイムで更新できます。

DoEvents() によって「修正」された状況に遭遇した場合、それは単純に、メイン UI スレッドで実行しようとしているコードが多すぎることを意味します。メイン UI スレッドは、ユーザー インタラクションへの応答と表示の更新に重点を置く必要があります。メインの UI スレッドが本来の仕事に戻ることができるように、制御イベント ハンドラーのコードは短くて適切なものにする必要があります。

適切な修正は、その長いコードを別のスレッドに移動することです。これにより、メインの UI スレッドが応答し、それ自体を更新できるようになります。多くのシナリオでは、BackgroundWorker()を配置するのが最も簡単な方法です。フォームを制御し、DoWork()、ProgressChanged()、および RunWorkerCompleted() イベントを関連付けます。*ただし、ProgressChanged() イベントを処理するには、WorkerReportsProgress() プロパティを true に設定する必要があります。後者の 2 つのイベントは既にメインの UI スレッドにマーシャリングされているため、クロススレッドの例外について心配する必要はありません。DoWork() ハンドラーから ReportProgress() を呼び出し、進行状況のパーセンテージ値とオプションのその他のオブジェクト (何でもかまいません) を渡します。これらの値は ProgressChanged() イベントで取得でき、GUI の更新に使用できます。RunWorkerCompleted() イベントは、DoWork() ハンドラー内のすべての作業が終了したときに発生します。

あなたの場合、作業を行っている別のクラスがあります。そのクラスで独自のスレッドを手動で作成して作業を行うことにより、BackgroundWorker が行うことをミラーリングできます。進行状況を更新したい場合はCustom Event、メイン フォームがサブスクライブするクラスを発生させます。ただし、そのイベントが受信されると、別のスレッドのコンテキストで実行されます。次に、コントロールを更新する前にコードがメイン UI スレッドで実行されるように、スレッド境界を越えて呼び出しを "マーシャリング" する必要があります。これは、デリゲート (メソッドへの「ポインター」) と Invoke() メソッドを使用して実現されます。* SynchronizationContext など、このタスクを実行する他の方法もあります。

これらのアプローチの例については、こちらを参照してください。

最後に、別のスレッドからカスタム イベントを発生させるクラスの非常に単純な例を次に示します。

public partial class Form1 : Form
{

    private Clock Clk;

    public Form1()
    {
        InitializeComponent();

        Clk = new Clock();
        Clk.CurrentTime += new Clock.TimeHack(Clk_CurrentTime);
    }

    private void Clk_CurrentTime(string hack)
    {
        if (label1.InvokeRequired)
        {
            Clock.TimeHack t = new Clock.TimeHack(Clk_CurrentTime);
            label1.Invoke(t, new object[] { hack });
        }
        else
        {
            label1.Text = hack;
        }
    }

}

public class Clock
{
    public delegate void TimeHack(string hack);
    public event TimeHack CurrentTime;

    private Thread t;
    private bool stopThread = false;

    public Clock()
    {
        t = new Thread(new ThreadStart(ThreadLoop));
        t.IsBackground = true; // allow it to be shutdown automatically when the application exits
        t.Start();
    }

    private void ThreadLoop()
    {
        while (!stopThread)
        {
            if (CurrentTime != null)
            {
                CurrentTime(DateTime.Now.ToString());
            }
            System.Threading.Thread.Sleep(1000);
        }
    }

    public void Stop()
    {
        stopThread = true;
    }

}
于 2013-05-17T13:04:58.513 に答える
0
public void UpdateLabel(String newLabel)
{
   lblCurProgress.Text = newLabel;
   lblCurProgress.Refresh();
}
于 2013-05-17T13:06:07.777 に答える