C#/WPF:在启动时创建并运行一个在单独线程上的事件

3
我一直试图避免在stackoverflow上问一些看似琐碎的问题。上次我问问题时,得到了许多负面回应,所以我想试着自己解决这个问题。所以大约一个月后,通过阅读两本书和一些视频教程,我仍然很困惑。 :)
据我的调试器显示,MainWindow.xaml.cs类的第39行被调用,但第30或31行似乎没有触发UI上的任何内容,有一次它确实触发了,但同时也给了我一个运行时错误。数周的困惑之后,我有点休息了一下,转而去做其他事情,所以我不确定我做了什么来消除运行时错误。所以,现在我请求帮助:)
更新
MainWindow.xaml.cs第45行返回异常:
“object because a different thread owns it.”
我的MIDI类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NAudio.Midi;

namespace StaveHelper
{
public sealed class  MIDIMain 
{
    private static MIDIMain midiMain = null;
    public static int noteOnNumber;
    public static int noteOffNumber;
    public MidiIn midiIn;
    public bool noteOn;
    private bool monitoring;
    private int midiInDevice;

    private MIDIMain()
    {
        GetMIDIInDevices();
    }

    public static MIDIMain GetInstance()
    {
        if (null == midiMain)
        {
            midiMain = new MIDIMain();
        }
        return midiMain;
    }


    public string[] GetMIDIInDevices()
    {
        //Get a list of devices
        string[] returnDevices = new string[MidiIn.NumberOfDevices];

        //Get the product name for each device found
        for (int device = 0; device < MidiIn.NumberOfDevices; device++)
        {
            returnDevices[device] = MidiIn.DeviceInfo(device).ProductName;
        }
        return returnDevices;
    }

    public void StartMonitoring(int MIDIInDevice)
    {
        if (midiIn == null)
        {
            midiIn = new MidiIn(MIDIInDevice);
        }
        midiIn.Start();
        monitoring = true;
    }

    public void midiIn_MessageReceived(object sender, MidiInMessageEventArgs e)
    {
        //int noteNumber;
        // Exit if the MidiEvent is null or is the AutoSensing command code  
        if (e.MidiEvent != null && e.MidiEvent.CommandCode == MidiCommandCode.AutoSensing)
        {
            return;
        }

        if (e.MidiEvent.CommandCode == MidiCommandCode.NoteOn)
        {
            // As the Command Code is a NoteOn then we need 
            // to cast the MidiEvent to the NoteOnEvent  
            NoteOnEvent ne;
            ne = (NoteOnEvent)e.MidiEvent;
            noteOnNumber = ne.NoteNumber;
        }

        if (e.MidiEvent.CommandCode == MidiCommandCode.NoteOff)
        {
            NoteEvent ne;
            ne = (NoteEvent)e.MidiEvent;
            noteOffNumber = ne.NoteNumber;
        }        
    }

    //// send the note value to the the MainWindow for display
    //public int sendNoteNum(int noteNumber)
    //{
    //    noteOnNumber = noteNumber;
    //    noteOn = true;
    //    return noteOnNumber;
    //}
}

我的 MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using NAudio.Midi;
using System.Threading;
using System.Windows.Threading;

namespace StaveHelper
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public  partial class MainWindow : Window
{
    Config config;
    MIDIMain midiMain;

    public delegate void mon(object sender, MidiInMessageEventArgs e);
    public MainWindow()
    {
        this.InitializeComponent();

        // Insert code required on object creation below this point.
        midiMain = MIDIMain.GetInstance();
        config = new Config();
        config.load_MIDIIn_Devices();
        //Thread t = new Thread(monitorNotes);
        midiMain.midiIn.MessageReceived += new EventHandler<MidiInMessageEventArgs>(monitorNotes); 
    }

    public void monitorNotes(object sender, MidiInMessageEventArgs e)  //LINE 39: MONITOR NOTES
    {

        switch ( MIDIMain.noteOnNumber)
        {
            case 30:
                C3.Opacity = 100;               //LINE 45: "The calling thread cannot access this 
                C3Dot.Opacity = 100;            //object because a different thread owns it."
                break;
            case 31:
                D3Dot.Opacity = 100;
                break;
        }
    }

    ~MainWindow()
    {

    }

    private void btnConfig_Click(object sender, RoutedEventArgs e)
    {
        config.Show();
    }


}
}

所以,答案似乎是改变:
switch ( MIDIMain.noteOnNumber)
    {
        case 30:
            C3.Opacity = 100;               //LINE 45: "The calling thread cannot access this 
            C3Dot.Opacity = 100;            //object because a different thread owns it."
            break;
        case 31:
            D3Dot.Opacity = 100;
            break;
    }
}

转换为

switch ( MIDIMain.noteOnNumber)
        {
            case 60:
                C3.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.Normal,
                    new System.Windows.Threading.DispatcherOperationCallback
                        (delegate
                        {
                            C3.Opacity = 100;
                            C3Dot.Opacity = 100;
                            MIDIMain.noteOffNumber = -1;
                            return null;
                        }), null);
                 break;
            case 61:
                D3Dot.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.Normal,
                    new System.Windows.Threading.DispatcherOperationCallback
                        (delegate
                        {
                            D3Dot.Opacity = 100;
                            D3Dot.Opacity = 100;
                            MIDIMain.noteOnNumber = -1;
                            return null;
                        }), null);
                break;
        }

感谢您的所有帮助!

说实话,我现在只记得它与在UI线程上使用MIDI类有关,因为现在甚至都不会发生了。现在发生的是我的switch语句中的case数字没有被调用。 - Laserbeak43
你尝试过在调试代码时设置断点并查看MIDIMain.noteOnNumber的值吗? - Rohit Vats
是的:它说“noteOnNumber在此上下文中不存在”。 - Laserbeak43
调用 midiIn_MessageReceived() 的人,我没有看到任何事件。 - sll
MainWindow.xaml.cs的第36行 - 构造函数 - Laserbeak43
但是 midiIn 是在哪里实例化的?看起来只有在 StartMonitoring() 中实例化了? - sll
2个回答

2
你的异常出现是因为你试图从后台线程修改WPF GUI组件。你需要使用Dispatcher。在此处有很多Stack Overflow问题可以提供帮助。例如,你可以使用这个回答中的代码。
yourControl.Dispatcher.BeginInvoke(
   System.Windows.Threading.DispatcherPriority.Normal, 
   new System.Windows.Threading.DispatcherOperationCallback(delegate
   {
        // update your GUI here    
        return null;
   }), null);

你好 Mark,那真的做到了!这是很多对象需要理解...哇!我还有很多东西要学习! - Laserbeak43

1

如果您查看StartMonitoring()方法 - 在midiIn为空的情况下,它会创建一个新实例,然后调用Start(),但在这种情况下没有人订阅MessageReceived,因此似乎您忘记了在这种情况下订阅midiIn.MessageReceived事件,因此midiIn_MessageReceived方法从未被调用。因此,noteOnNumber保持未分配状态,因为只有在此方法(midiIn_MessageReceived)中,我看到一个代码将值分配给noteOnNumber变量。

尝试更新StartMonitoring()方法:

if (midiIn == null)
{
   midiIn = new MidiIn(MIDIInDevice);
   midiIn.MessageReceived += midiIn_MessageReceived;
}

啊,好的...非常抱歉让您白忙一场,但似乎我之前在键盘上按错了按键!!!(尽管之前我按下了每个键,但没有得到任何东西。)我收到的运行时异常是 “调用线程无法访问此对象,因为不同的线程拥有它。” - Laserbeak43

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接