2017-12-29 63 views
0

私のクラスの1つを10秒間私のウィンドウに点滅する赤線で描画しようとしていますが、graphics.DrawLineSystem.ArgumentException: parameter is not validというエラーが表示されます。問題を見つけようとすると、最小限の部品を含めて再現しています。 reddark関数は、タイマーのaTickイベントの外で線を完全に描画しますが、それによってアクティブ化されている間は上記のエラーが発生します。グラフィックスやペンオブジェクトが無効なときにこのエラーが発生する人もいますが、私にとってはこれは当てはまりません。タイマーのgraphics.DrawLineで 'System.ArgumentExceptionパラメータが有効ではありません'

私のコードについて:私は非常に最近になってプログラミングを始めました。データバインディングに関する伝説だけを聞いたことがあり、コードを簡略化できたと思いますが、それは私の能力の範囲外です。 boolがtrueになったときにアクションを実行してから(それをfalseに戻す)回避策があります。これは私が点滅を開始するために、またタイマーの各ティックで私のグラフィックスを再描画するために使用しているものです。 aTickイベントの最後にredrawを変更しようとしたときに、Cannot use ref or out parameter 'redraw' inside an anonymous method, lambda expression, or query expressionと表示されていたので、私はまた2番目のRedrawブールが必要でした。あなたが見るように、私はその2番目のブールを追加することでその周りに取り組みましたが、なぜそれが起こっているのか、それに対してより良い解決策があるのか​​を私に説明することができれば素晴らしいでしょう。ここで

は私のフォームのコードです:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace GrafikaTeszt 
{ 
    public partial class Form1 : Form 
    { 

     bool flash = false; //can we draw the line? 
     bool redraw = false; //should we redraw? 

     public Form1() 
     { 
      InitializeComponent(); 
     } 

     Class1 classic = new Class1(); 

     private void Form1_Paint(object sender, PaintEventArgs e) 
     { 
      if (flash) 
      { 
       classic.makeitflash(e.Graphics, out redraw); 

       if (redraw) 
       { 
        Invalidate(); 
       } 
      } 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      flash = true; 
      Invalidate(); 
     } 
    } 
} 

そして、ここで私は線を描画しようとしている元のクラスからのコードです:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 
using System.Drawing; 

namespace GrafikaTeszt 
{ 
    class Class1 
    { 
     Timer clock; 
     int ticks; 

     public void makeitflash(Graphics g, out bool redraw) 
     { 
      redraw = false; 
      bool Redraw = false; 
      ticks = 0; 
      clock.Start(); 
      clock.Tick += new EventHandler(aTick); 

      void aTick(object sender, EventArgs e) 
      { 
       if (ticks % 2 == 0) 
       { 
        red(); //draw a red line 
       } 
       else 
       { 
        dark();  //draw a darkred line 
       } 

       if (ticks == 20) 
       { 
        clock.Stop(); 
       } 
       ticks++; 
       Redraw = true; 
      } 
      void red() { g.DrawLine(Pens.Red, 100, 100, 500, 500); } 
      void dark() { g.DrawLine(Pens.DarkRed, 100, 100, 500, 500); } 

      redraw = Redraw; 
     } 

     public Class1() 
     { 
      clock = new Timer(); 
      clock.Interval = 200; 
     } 
    } 
} 
+2

最近追加されたC#言語機能がスパゲッティコードの書き方にどれほど最近追加されたかを知ることができます。 Paintイベントが発生した後、e.Graphicsオブジェクトが無効になるため、クラッシュします。まともなチュートリアルやWinformsプログラミングの入門書が役立ちます。 –

答えて

0

あなたのコードは、Windowsのペイントを使用していますイベントが使用されることは意図されていません。

でも、Form.Paintイベントハンドラの引数として渡されるオブジェクトは、単一のイベントの期間だけ有効です。あなたのコードはそれをタイマーに渡します。タイマーは、イベントハンドラが終了してからずっとアクセスして使用しようとします。

第2の問題は、Redraw/redraw変数を混乱させることです。塗りつぶし領域は、Paintイベントハンドラ内から無効にしないでください。

タイマーに点滅のステートマシンを処理させ、Invalidateにコールさせます。次に、Paintイベントハンドラの内部から状態を読み取り、それに応じて描画します。 MSDNにも便利なものがあります。examples

0

Hans Passant氏の診断(正確な時点で無効にした場合、eGraphicsは機能によってしかアクセスできません)によれば、プログラムの再構成後に問題を解決することができました。私のクラスの大半(:()を公開することで、フォームにタイマーを入れても、私のクラスにアクセスすることができました。フォームにタイマーを置いて、毎回無効にすることができます。今ここに新しいコードは次のとおりです。 フォーム:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace GrafikaTeszt 
{ 
    public partial class Form1 : Form 
    { 
     Timer clock; 
     Class1 classic; 
     bool stop; 

     public Form1() 
     { 
      InitializeComponent(); 
      clock = new Timer(); 
      clock.Interval = 200; 
      clock.Tick += new EventHandler(ticked); 
      classic = new Class1(); 
      stop = false; 
     } 

     void ticked(object sender, EventArgs e) 
     { 
      classic.ticks++; 
      Invalidate(); 
     } 

     private void Form1_Paint(object sender, PaintEventArgs e) 
     { 
      if (classic.flashing) 
      { 
       classic.draw(e.Graphics, out stop); 
       if (stop) 
       { 
        clock.Stop(); 
        classic.flashing = false; 
        Invalidate(); 
       } 
      } 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      clock.Start(); 
      classic.flashing = true; 
      classic.ticks = 0; 
     } 
    } 
} 

クラス:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 
using System.Drawing; 

namespace GrafikaTeszt 
{ 
    class Class1 
    { 
     public int ticks; 
     public bool flashing; 

     public void draw(Graphics g, out bool stop) 
     { 
      stop = false; 
      if (ticks % 2 == 0) 
      { 
       red(); //draw a red line 
      } 
      else 
      { 
       dark();  //draw a darkred line 
      } 

      if (ticks == 20) 
      { 
       stop = true; 
      } 

      void red() { g.DrawLine(Pens.Red, 100, 100, 500, 500); } 
      void dark() { g.DrawLine(Pens.DarkRed, 100, 100, 500, 500); } 
     } 

     public Class1() 
     { 
      flashing = false; 
     } 
    } 
} 

はあなたの助けをありがとう

1

あなたはあまりにもハードなものを作っているthe other answerが提供する基本的な診断。正解ですが、あなたはそれに気づきました問題を過度に考えてあなたの新しいバージョンは優れていますが、まだ複雑すぎます。現代のasync/awaitイディオムを使用することができません。イディオムは、非同期コード(タイマーを含むコードなど)を線形/同期の方法で書き込むのに使用できます。ここで

は私見である、あなたのコードのバージョンでより良い大幅簡単かつです:

public partial class Form1 : Form 
{ 
    private Pen _currentPen = Pens.Black; 

    public Form1() 
    { 
     InitializeComponent(); 
    } 

    protected override void OnPaint(PaintEventArgs e) 
    { 
     base.OnPaint(e); 

     e.Graphics.DrawLine(_currentPen, 100, 100, 500, 500); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     // Ignore returned task...nothing more to do. 
     var task = FlashLine(TimeSpan.FromMilliseconds(200), TimeSpan.FromSeconds(4)); 
    } 

    private async Task FlashLine(TimeSpan interval, TimeSpan duration) 
    { 
     TimeSpan nextInterval = interval; 
     Stopwatch sw = Stopwatch.StartNew(); 
     bool red = true; 

     while (sw.Elapsed < duration) 
     { 
      TimeSpan wait = nextInterval - sw.Elapsed; 

      // Just in case we got suspended long enough that the 
      // next interval is already here 
      if (wait > TimeSpan.Zero) 
      { 
       // "await" will suspend execution of this method, returning 
       // control to the caller (i.e. freeing up the UI thread for 
       // other UI activities). This method will resume execution 
       // when the awaited task completed (in this case, a simple delay) 
       await Task.Delay(wait); 
      } 

      _currentPen = red ? Pens.Red : Pens.Black; 
      red = !red; 
      Invalidate(); 

      // Just in case it the operation took too long and the initial next 
      // interval time is still in the past. Use "do/while" to make sure 
      // interval is always incremented at least once, because Task.Delay() 
      // can occasionally return slightly (and imperceptibly) early and the 
      // code in this example is so simple, that the nextInterval value might 
      // still be later than the current time by the time execution reaches 
      // this loop. 
      do 
      { 
       nextInterval += interval; 
      } while (nextInterval < sw.Elapsed); 
     } 

     _currentPen = Pens.Black; 
     Invalidate(); 
    } 
} 

上記の最も複雑な要素が、私は明らかにできるだけ近い点滅を確認するために追加の論理です希望の200ms間隔。

private async Task FlashLine(TimeSpan interval, TimeSpan duration) 
    { 
     int iterations = (int)(duration.TotalSeconds/interval.TotalSeconds); 
     bool red = true; 

     while (iterations-- > 0) 
     { 
      await Task.Delay(interval); 

      _currentPen = red ? Pens.Red : Pens.Black; 
      red = !red; 
      Invalidate(); 
     } 

     _currentPen = Pens.Black; 
     Invalidate(); 
    } 

いずれかの方法で、:事実はあなたが点滅するが、多分オフミリ秒(何か何人のユーザーがこれまで気づかないだろう)の数十を巻き上げるを可能にするために喜んでいる場合は、単純に、さらにほぼ同じ結果を得ることができ、ありますこれは、Timerを使用するよりもはるかに優れており、すべてのロジックを処理する全く新しいクラスを作成し、ラインを描画するためのローカルメソッドを使用するだけです。もちろん、実際にローカルメソッドと別のクラスを使用することを決定したとしても、Timerの混乱を避けるために、上記を簡単にリファクタリングすることができます。

+1

非同期プログラミングは私が学びたいことのリストに載っているので、インターネット上のほとんどの例は基本的知識だけでは使いにくいので、私はそれで解決された既知の問題を見てうれしいです。そして、その別のクラスでは、線の終端(レーザービーム:D)がそこで計算できるので、元のプログラムで意味があります。 –

関連する問題