J
J
Just0x322021-12-08 14:50:25
WPF
Just0x32, 2021-12-08 14:50:25

Why is one of the third party thread events being ignored?

[Irrelevant methods and fields have been truncated in the code]

Model.cs

...
namespace Timetronome
{
    public class Model : INotifyPropertyChanged
    {
        ...
        private string toFilePath = "click.wav";

        Thread timerThread;
        Thread clickerThread;

        MediaPlayer mediaPlayer;
        MediaPlayer mediaChecker;

        public Model (int receivedTempo, int receivedTime)
        {
            SettedTempo = receivedTempo;
            SettedTime = receivedTime;

            clickerThread = new Thread(new ThreadStart(Clicker));
            timerThread = new Thread(new ThreadStart(Timer));

            //CheckMediaFile(toFilePath);

            clickerThread.Start();
            timerThread.Start();
        }

        public int EstimateTime
        {
            get => estimateTime;
            private set
            {
                estimateTime = value;
                OnPropertyChanged();
            }
        }

        public bool IsMediaFailed
        {
            get => isMediaFailed;
            private set
            {
                isMediaFailed = value;
                OnPropertyChanged();
            }
        }

        public void ToogleMetronomeState(int receivedTempo, int receivedTime)
        {
            //CloseMediaChecker();

            if (!IsMetronomeRunned)
            {
                SettedTempo = receivedTempo;
                SettedTime = receivedTime;

                RunMetronome();
            }
            else
            {
                StopMetronome();
            }
        }

        private void Clicker()
        {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.MediaFailed += NotifyMediaFailed;
            mediaPlayer.Open(new Uri(toFilePath, UriKind.Relative));

            int delay;

            while (!IsClosingApp)
            {
                ThreadWaiting();

                delay = 60000 / SettedTempo;

                while (!IsClosingApp && IsMetronomeRunned)
                {
                    mediaPlayer.Stop();
                    mediaPlayer.Play();

                    ThreadDelay(delay);
                }
            }

            mediaPlayer.Close();
        }

        private void Timer()
        {
            while (!IsClosingApp)
            {
                ThreadWaiting();

                EstimateTime = SettedTime;

                while (!IsClosingApp && IsMetronomeRunned && (EstimateTime > 0))
                {
                    ThreadDelay(60000);

                    EstimateTime--;
                }

                IsMetronomeRunned = false;
            }
        }

        private void CheckMediaFile(string toFilePath)
        {
            mediaChecker = new MediaPlayer();

            mediaChecker.MediaFailed += NotifyMediaFailed;

            mediaChecker.Open(new Uri(toFilePath, UriKind.Relative));
        }

        private void CloseMediaChecker() => mediaChecker?.Close();

        private void NotifyMediaFailed(object sender, ExceptionEventArgs e) => IsMediaFailed = true;

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private bool IsClosingApp [...]
        public bool IsMetronomeRunned [...]
        public int SettedTempo [...]
        public int SettedTime [...]
        private void RunMetronome() [...]
        private void StopMetronome() [...]
        private void ThreadDelay(int delay) [...]
        private void ThreadWaiting() [...]
        private void ThreadInterrupt(Thread threadVariable) [...]
        public void CloseApp() [...]
    }
}


ViewModel.cs

...
namespace Timetronome
{
    public class ViewModel : INotifyPropertyChanged
    {
        ...
        public ViewModel(string fromViewTempo, string fromViewTime)
        {
            FromViewTempo = ParseString(fromViewTempo, FromViewTempo);
            FromViewTime = ParseString(fromViewTime, FromViewTime);

            model = new Model(FromViewTempo, FromViewTime);
            model.PropertyChanged += ModelNotify;
        }

        public int EstimateTime { get => model.EstimateTime; }

        public bool IsMediaFailed { get => model.IsMediaFailed; }

        private void ModelNotify(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(e.PropertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private int FromViewTempo [...]
        private int FromViewTime [...]
        public bool IsMetronomeRunned [...]
        public int SettedTempo [...]
        public int SettedTime [...]
        private int ParseString(string inputString, int previousIntVariableValue) [...]
        public void ToggleMetronomeState(string fromViewTempo, string fromViewTime) [...]
        public void CloseApp() [...]
    }
}


MainWindow.xaml.cs

...
namespace Timetronome
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        ...
        public MainWindow()
        {
            InitializeComponent();
            
            StartStopButtonText = "Start";

            viewModel = new ViewModel("120", "5");
            DataContext = viewModel;

            this.Closing += MainWindowClosing;
            viewModel.PropertyChanged += ViewModelNotify;
        }

        public string StartStopButtonText
        {
            get => startStopButtonText;
            private set
            {
                startStopButtonText = value;
                OnPropertyChanged();
            }
        }

        private void ViewModelNotify(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsMetronomeRunned" || e.PropertyName == "EstimateTime")
                ChangeStartStopButtonText();

            if (e.PropertyName == "IsMediaFailed" && viewModel.IsMediaFailed)
                ShowMediaFailedMessage();
        }

        private void ChangeStartStopButtonText()
        {
            if (viewModel.IsMetronomeRunned)
                StartStopButtonText = "Est. time:" + Environment.NewLine + viewModel.EstimateTime + " min";
            else
                StartStopButtonText = "Start";
        }

        private void ShowMediaFailedMessage()
        {
            MessageBox.Show("click.wav is absent or broken!");
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged ([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private void StartStopButtonClick(object sender, RoutedEventArgs e) [...]
        private void MainWindowClosing(object sender, System.ComponentModel.CancelEventArgs e) [...]
    }
}


MainWindow.xaml

<Window
        x:Name="MainWindowClass"
        ...

    <Grid>
        ...
        <Button x:Name="StartStopButton" Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" Click="StartStopButtonClick" >
            <TextBlock x:Name="StartStopButtonTextBlock" TextWrapping="Wrap" TextAlignment="Center"
                       Text="{Binding StartStopButtonText, ElementName=MainWindowClass, UpdateSourceTrigger=PropertyChanged}"/>
        </Button>
    </Grid>
</Window>



I'm trying to "raise" a message from mediaPlayer from the Clicker method (from Model.cs to MainWindow.xaml.cs):
in the clickerThread thread, I subscribe with the NotifyMediaFailed method => I pass the value true to the IsMediaFailed property => I call the OnPropertyChanged method , which sends the message "to the top" ( to the main thread), along with other notifications. Writing to the EstimateTime property in the Timer method works in a similar way : in the timerThread thread, I pass some value to the EstimateTime property => I call the method


OnPropertyChanged , which sends the message "to the top".

Events are handled the same way up to MainWindow.xaml.cs .
But in the first case, the event is lost somewhere.
The key difference in processing on the MainWindow side:
- no binding directly to any element for the IsMediaFailed event ;
- the presence of a binding to the StartStopButton button through the StartStopButtonText property for the EstimateTime event.

If you uncomment the CheckMediaFile method in the constructor of the Model class , then the additional MediaPlayer called by itfrom the main thread will successfully deliver the message.

To recap the question: two side threads write to properties that are handled by the main thread, but why doesn't that work in one of the cases?

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question