2017-12-29 28 views
0

내 클래스 중 하나를 내 창에 10 초 동안 깜박이는 빨간색 선을 그려려고했지만 System.ArgumentException: parameter is not valid 오류가 발생했습니다. 내 graphics.DrawLine. 문제를 찾으려고 노력하면서, 나는 최소한의 부품을 포함하여 그것을 재창조하는 한까지 갔다. reddark 함수는 타이머의 aTick 이벤트 외부에서 선을 완벽하게 그릴 수 있지만이 함수로 활성화되는 동안 언급 된 오류를 제공합니다. 그래픽이나 펜 객체가 유효하지 않을 때이 오류가 발생하는 경우도 있지만, 여기서는 그렇지 않습니다.'System.ArgumentException 매개 변수가 유효하지 않습니다.'graphics.DrawLine 타이머에

내 코드에 대해 : 나는 아주 최근에 프로그래밍을 시작했으며, 데이터 바인딩에 대한 전설 만 들었고 코드를 간소화 할 수는 있었지만 실제로는 내 능력 밖이므로, bool이 true로 바뀌면 (다시 거짓으로 돌아서는) 작업을 수행하는 작업이 거의 불가능할 수도 있습니다. 그게 내가 깜박이 시작하고 또한 타이머의 각 진드기에서 내 그래픽을 다시 그리기 위해 사용하는거야. 또한 aTick 이벤트가 끝날 때 redraw을 변경하려고 시도했을 때 Cannot use ref or out parameter 'redraw' inside an anonymous method, lambda expression, or query expression이라고 했으므로 두 번째 Redraw bool이 필요했습니다. 내가 볼 수 있듯이 두 번째 부울을 추가하여 그 주위를 돌아 다니며 작업했지만, 왜 그런 일이 일어나고 더 나은 해결 방법이 무엇인지 설명 할 수 있다면 좋을 것입니다. 여기

은 내 양식의 코드입니다 :

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

귀하의 코드는 윈도우 페인트를 사용한다 이벤트가 사용되도록 의도되지 않은 방식으로

documentation may not mention it이지만 Form.Paint 이벤트 처리기에 인수로 전달 된 Graphics 개체는 단일 이벤트 기간 동안 만 유효합니다. 코드는 이벤트 처리기가 종료 된 후 오랫동안 액세스하여 사용하려고하는 타이머에 전달합니다.

두 번째 문제는 변수 Redraw/redraw의 혼란스러운 사용입니다. 페인트 영역은 Paint 이벤트 처리기 내에서 무효화되어서는 안됩니다.

타이머가 깜박이는 상태 기계를 처리하도록하고 Invalidate을 호출하게하십시오. 그런 다음 Paint 이벤트 처리기 내부에서 상태를 읽고 그에 따라 그립니다. MSDN도이 경우 유용한 examples을 가지고 있습니다.

0

한스 패전트 (Hans Passant)의 진단 (e.Graphics는 정확한 순간에 무효화 된 경우에만 기능을 통해 액세스 할 수 있음)에 따르면 프로그램을 재구성 한 후에 문제를 해결할 수있었습니다. 대부분의 클래스를 공개 (:()로 만들면 타이머를 Form에 넣고 여전히 클래스에 액세스 할 수 있습니다. Form에 타이머가 있으면 각 체크마다 무효화하여 오른쪽에 e.Graphics를 사용할 수있게합니다. . 형태 : : 지금 여기에 새로운 코드의

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)을 계산할 수 있기 때문에 원본 프로그램에서 의미가 있습니다. –

관련 문제