C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[4]: 別スレッドでのコントロールへのアクセス


(過去ログ 144 を表示中)

[トピック内 11 記事 (1 - 11 表示)]  << 0 >>

■84440 / inTopicNo.1)  別スレッドでのコントロールへのアクセス
  
□投稿者/ KL (1回)-(2017/07/05(Wed) 17:40:30)

分類:[C#] 

2017/07/05(Wed) 19:18:25 編集(投稿者)
初めましてKLと申します。
現在大学の研究の一環としてプログラミングをVisualStudio2017のC#で行っておりどうしてもわからないことがあり投稿させていただきます。
クラスを生成しその中にSystem.Timers.Timerを持たせ、
そのタイマーがカウントするごとにクラスの中で生成したPictureBoxを移動させたいのですがうまくいきません。
以下のようなコード書いたのですが実行しボタンを押すと
「System.InvalidOperationException:
'有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'pictureBox1' がアクセスされました。'」
というエラーが出てきます。
また自分なりに調べてみるとタイマーにより別スレッドが作成されそこからではコントロールにアクセスできないということがわかり、
delegateやInvokeを使うことも試してみましたが
「System.InvalidOperationException: 
'ウィンドウ ハンドルが作成される前、コントロールで Invoke または BeginInvoke を呼び出せません。'」
というエラーが出てしまいます。

解決策やヒントをいただければ幸いです。よろしくお願いします。

//以下ソースコード----------------------------------------------------------------------
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Timers;

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        Class1 move = new Class1();
        public Form1()
        {
            InitializeComponent();
            move = new Class1 { myPictureBox = pictureBox1 };
            move.NewTimer();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            move.myTimer1.Start();
        }

    }

    public class Class1 :Form
    {
        public PictureBox myPictureBox;
        public System.Timers.Timer myTimer1;

        public void NewTimer()
        {
            myTimer1 = new System.Timers.Timer();
            myTimer1.Enabled = false;
            myTimer1.AutoReset = true;
            myTimer1.Interval = 100;
            myTimer1.Elapsed += new ElapsedEventHandler(OnTimerEvent1);
        }

        public void OnTimerEvent1(object source, ElapsedEventArgs e)
        {
            MovePictureBox();
        }
    
    //delegate void MoveDelegate();
        public void MovePictureBox()
        {
            /*if(myPictureBox.InvokeRequired)
            {   
                Invoke(new MoveDelegate(MovePictureBox));
          return;
      }*/
            Point p;
            p = myPictureBox.Location;
            p.X += 10;
            myPictureBox.Location = p;
        }
    }
}

追記:
説明不足な点がいくつかありましたので追記させていただきます。
・このコードを書く前にWindows.Form.Timerの方で正常に動作することは確認しました。
・コメントにてInvokeとdelegateを使ったときのコードを加えました。

引用返信 編集キー/
■84441 / inTopicNo.2)  Re[1]: 別スレッドでのコントロールへのアクセス
□投稿者/ とっちゃん (443回)-(2017/07/05(Wed) 17:46:55)
No84440 (KL さん) に返信
> そのタイマーがカウントするごとにクラスの中で生成したPictureBoxを移動させたいのですがうまくいきません。
> 以下のようなコード書いたのですが実行しボタンを押すと
> 「System.InvalidOperationException:
> '有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'pictureBox1' がアクセスされました。'」
> というエラーが出てきます。
> また自分なりに調べてみるとタイマーにより別スレッドが作成されそこからではコントロールにアクセスできないということがわかり、
> delegateやInvokeを使うことも試してみましたが
> 「System.InvalidOperationException:
> 'ウィンドウ ハンドルが作成される前、コントロールで Invoke または BeginInvoke を呼び出せません。'」
> というエラーが出てしまいます。
>
Invoke をどういう形で書きましたか?
それによって上述のエラーが出ているのかもしれません。

が、もっと安直な解決策としては、Forms 名前空間のタイマーを使うという方法があります。

こちらの場合、UIスレッドでタイマー通知が来るため、そのまま各コントロールを操作できます。
ただし、その代償としてタイマーの精度が落ちます。

とはいえ、100ミリ秒(0.1秒)程度の頻度でコントロールを移動させるのであれば、問題はないと思います。

学校の課題のようなのであえて答え的なものについては何も書きませんので、まずはいろいろと調べることをお勧めします。


ヒントは、System.Windows.Forms 名前空間にある Timer クラスを利用する。


です。


引用返信 編集キー/
■84442 / inTopicNo.3)  Re[1]: 別スレッドでのコントロールへのアクセス
□投稿者/ 魔界の仮面弁士 (1332回)-(2017/07/05(Wed) 18:01:35)
No84440 (KL さん) に返信
> クラスを生成しその中にSystem.Timers.Timerを持たせ、
System.Timers.Timer の代わりに
System.Windows.Forms.Timer を使ってみてください。

そうすれば、スレッド間のやりとりを意識せずに済みます。


(1) Form1 に、ツールボックスから [Timer] を貼り付けます。
 フォームの下部に、timer1 が貼り付きます。

(2) timer1 をダブルクリックして、Tick イベントの定義を開きます。
 そこに下記の記述を行います。

 private void timer1_Tick(object sender, EventArgs e)
 {
  pictureBox1.Left += 10;
 }

(3) tiemr1 を起動するコードを記述します。

 private void button1_Click(object sender, EventArgs e)
 {
  // timer1.Interval = 100; // Interval はデザイン時に設定済み
  timer1.Start();      // timer1.Enabled = true; でも OK
 }



> public class Class1 :Form
このクラスは、Form を継承する必要はない筈です。
public class Class1 で十分かと。

仮に Form 継承クラスを作るのだとしても、Class1.cs を別途用意して、
その中に記載されることをお奨めします。
同じファイル内に複数の Form を定義してしまうと、
Visual Studio の フォームデザイナーの支援を受け難くなりますから。
引用返信 編集キー/
■84443 / inTopicNo.4)  Re[1]: 別スレッドでのコントロールへのアクセス
□投稿者/ furu (103回)-(2017/07/05(Wed) 18:18:29)
No84440 (KL さん) に返信
> delegateやInvokeを使うことも試してみましたが

delegateとInvokeで問題なく動きました。

ただ、public class Class1の後の
:Form
は、余計かな。

頑張ってください。
引用返信 編集キー/
■84445 / inTopicNo.5)  Re[2]: 別スレッドでのコントロールへのアクセス
□投稿者/ KL (2回)-(2017/07/05(Wed) 18:59:32)
No84441 (とっちゃん さん) に返信
返答ありがとうございます。

> Invoke をどういう形で書きましたか?
> それによって上述のエラーが出ているのかもしれません。

以下のように書きました。
delegate void MoveDelegate();
public void MovePictureBox()
        {
      if(myPictureBox.InvokeRequired)
            {   
                Invoke(new MoveDelegate(MovePictureBox));
          return;
      }
            Point p;
            p = myPictureBox.Location;
            p.X += 10;
            myPictureBox.Location = p;
        }

> が、もっと安直な解決策としては、Forms 名前空間のタイマーを使うという方法があります。

実はこの前にFormのタイマーを使ってやっていましてそれはうまくいきました。
そのあとでタイマーにいくつか種類があると知り精度がよくなるなら試してみようと思って分からなくなってしまいました。

引用返信 編集キー/
■84446 / inTopicNo.6)  Re[2]: 別スレッドでのコントロールへのアクセス
□投稿者/ KL (3回)-(2017/07/05(Wed) 19:05:07)
No84442 (魔界の仮面弁士 さん) に返信
返答ありがとうございます。
> System.Windows.Forms.Timer を使ってみてください。
一回Forms.Timerでは動作を確認した後でTimers.Timerで同じことをしようと思って躓いてしまいました。
そこのところ説明不足で申し訳なかったです。

> このクラスは、Form を継承する必要はない筈です。
> public class Class1 で十分かと。
>
> 仮に Form 継承クラスを作るのだとしても、Class1.cs を別途用意して、
> その中に記載されることをお奨めします。
> 同じファイル内に複数の Form を定義してしまうと、
> Visual Studio の フォームデザイナーの支援を受け難くなりますから。

安易にFormは継承させない方がいいんですね。勉強になりました。
引用返信 編集キー/
■84447 / inTopicNo.7)  Re[2]: 別スレッドでのコントロールへのアクセス
□投稿者/ KL (4回)-(2017/07/05(Wed) 19:08:34)
No84443 (furu さん) に返信
返答ありがとうございます。
> delegateとInvokeで問題なく動きました。
できればdelegateとInvokeをどこで使えばいいのか教えていただけないでしょうか。

> ただ、public class Class1の後の
> :Form
> は、余計かな。
ご指摘ありがとうございます。気を付けます。
引用返信 編集キー/
■84448 / inTopicNo.8)  Re[3]: 別スレッドでのコントロールへのアクセス
□投稿者/ furu (104回)-(2017/07/05(Wed) 20:04:31)
No84447 (KL さん) に返信
> ■No84443 (furu さん) に返信
> できればdelegateとInvokeをどこで使えばいいのか教えていただけないでしょうか。

答えになってしまいますが

    Point    p;
    delegate void SetLocationsDelegate();

    void SetLocations()
    {
      myPictureBox.Location = p;
    }

    public void MovePictureBox()
    {
        p = myPictureBox.Location;
        p.X += 10;
        myPictureBox.Invoke(new SetLocationsDelegate(SetLocations));
        
    }

引用返信 編集キー/
■84449 / inTopicNo.9)  Re[3]: 別スレッドでのコントロールへのアクセス
□投稿者/ 魔界の仮面弁士 (1333回)-(2017/07/05(Wed) 20:12:53)
No84446 (KL さん) に返信
> 一回Forms.Timerでは動作を確認した後でTimers.Timerで同じことをしようと思って躓いてしまいました。
> そこのところ説明不足で申し訳なかったです。

タイマーの停止および処分のコードを加えてみました。


// ----- Form1.cs -----
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  public partial class Form1 : Form
  {
    Class1 move;
    public Form1()
    {
      InitializeComponent();
      if (this.components == null)
      {
        this.components = new Container();
      }
      move = new Class1(this.components);
    }

    private void button1_Click(object sender, EventArgs e)
    {
      move.Start(pictureBox1);
    }

    private void button2_Click(object sender, EventArgs e)
    {
      move.Stop();
    }
  }
}


// ----- Class1.cs -----
using System;

namespace WindowsFormsApp
{
  class Class1 : System.ComponentModel.Component
  {
    private System.Timers.Timer myTimer1;
    private System.Windows.Forms.PictureBox myPictureBox;

    public Class1()
    {
      myTimer1 = new System.Timers.Timer();
      myTimer1.Enabled = false;
      myTimer1.AutoReset = true;
      myTimer1.Interval = 100;
      myTimer1.Elapsed += OnTimerEvent1;
    }

    public Class1(System.ComponentModel.IContainer container) : this()
    {
      container.Add(this);
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing && myTimer1 != null)
      {
        myTimer1.Dispose();
        myTimer1 = null;
      }
      base.Dispose(disposing);
    }

    public void Start(System.Windows.Forms.PictureBox target)
    {
      myPictureBox = target;
      myTimer1.Start();
    }

    public void Stop()
    {
      myTimer1.Stop();
    }

    private void OnTimerEvent1(object sender, System.Timers.ElapsedEventArgs e)
    {
      if (myPictureBox != null)
      {
        myPictureBox.Invoke(new System.Windows.Forms.MethodInvoker(MovePictureBox));
      }
    }

    protected void MovePictureBox()
    {
      myPictureBox.Left += 10;
    }
  }
}
引用返信 編集キー/
■84452 / inTopicNo.10)  Re[4]: 別スレッドでのコントロールへのアクセス
□投稿者/ KL (5回)-(2017/07/06(Thu) 09:20:39)
No84448 (furu さん) に返信
ありがとうございます!
参考にして書いてみます。

引用返信 編集キー/
■84453 / inTopicNo.11)  Re[4]: 別スレッドでのコントロールへのアクセス
□投稿者/ KL (6回)-(2017/07/06(Thu) 09:21:34)
No84449 (魔界の仮面弁士 さん) に返信
>
> タイマーの停止および処分のコードを加えてみました。

わざわざありがとうございます。
参考にしながら書いてみます。
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -