【问题标题】:How to allow a button to stop an action AND have that action affect form controls如何允许按钮停止操作并使该操作影响表单控件
【发布时间】:2015-11-23 15:35:17
【问题描述】:

所以基本上,我正在创建音乐软件,并且希望能够在必要时暂停/停止歌曲。但是,当歌曲播放时,它不允许使用表单上的任何控件,并且“KeyDown”事件似乎也没有影响,因此在歌曲结束之前我无法停止歌曲。

我尝试使用“线程”类的实例来执行此操作,但问题是我需要在表单上使用某些“标签”控件,这似乎很难从另一个线程中完成。

这是我的“播放”代码的要点。

        Sound[] notes = song.Sounds;
        double seconds = 0;
        foreach (Sound sound in notes)
        {
            seconds += (double)sound.NoteType.GetDuration(tempo) / 1000;
        }
        timeleft = seconds;
        lblSecondsLeft.Text = timeleft + "";
        double length = notes.Length;
        double thing = (100 / length);
        double otherthing = Math.Round(thing);
        sounds = notes;
        double progress = 0;
        for (int i = 0; i < notes.Length; i++)
        {
            Sound sound = notes[i];
            lblTempo.Text = tempo.ToString();
            string note = sound.Note.ToString();
            lblNote.Text = note;
            progress += thing;
            lblProgress.Text = progress + "%";
            seconds -= (double)sound.NoteType.GetDuration(tempo) / 1000;
            lblSecondsLeft.Text = seconds + " seconds left";
            sound.PlaySound(song.Channel);
        }

【问题讨论】:

  • 好吧,您在 UI 线程上同步运行所有内容 - 当然不能使用 UI。 Sound 类(不管是什么)有异步 API 吗?不过,逐个音符演奏可能效果并不好……
  • 我想说的是我之前试图在另一个线程中运行这段代码,因此它已经是异步的。
  • 嗯,你不会摆脱使用不同的线程。但是如果你使用 Invoke 从另一个线程访问控件是没有问题的。
  • 嗯,最简单的方法是将演示代码与实际播放分开。然后你可以使用例如IProgress&lt;T&gt; 根据需要发布任何 UI 更新。尽管无论如何您可能希望使用基于拉取的方法,因为您当前的方法是发布太多更新(当注释很短时)或太少(当注释很长时)。读取当前状态和更新 UI 的计时器可能会更好。
  • 添加到@Luaan 最后的评论,您可以添加事件NextNote 来更新进度(例如显示当前播放的音符以及还剩多少音符/秒)和事件Started/Finished(用于明显的原因)到您的模型(用于播放歌曲)。

标签: c# multithreading winforms


【解决方案1】:

这是一个烦恼,但这是一个众所周知的烦恼。您必须在单独的(非 UI)线程上运行非 UI 代码,以便 UI 保持对按钮和文本更新等的响应。但是,这意味着您必须通过在更新期间切换到 UI 线程来从非 UI 线程更新 UI。下面的代码就是这样做的。

注意 1:这是从我必须手头的一些代码中剪裁出来的,因此没有正确检查。

注2:这是WPF版本,WinForms略有不同,但原理相同。

   public String LabelText{
        get {
            if (!this.Dispatcher.CheckAccess()) {
                return (String)this.Dispatcher.Invoke(new Func<String>(() => { return LabelText; }));
            }
            return Label.Text;
        }
        set {
            if (!this.Dispatcher.CheckAccess()) {
                this.Dispatcher.Invoke(new Action(() => { LabelText = value; }));
                return;
            }
            LabelText = value;
        }
    }

这通过检查 get/set 是否在正确的线程上起作用,如果不是,则切换线程并调用自身(现在在正确的线程上),完成 get 或 set 并返回。如果您不需要线程等待屏幕更新,您也可以使用 BeginInvoke,实时音乐可能就是这种情况。

【讨论】:

  • 对此的进一步说明,出于某种奇怪的原因,CheckAccess 不会作为 Intellisense 中的 Dispatcher 方法出现,您只需假设它存在并且一切正常。
猜你喜欢
  • 2021-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-29
  • 1970-01-01
  • 1970-01-01
  • 2011-10-18
  • 1970-01-01
相关资源
最近更新 更多