I have a JobView which contains a ItemsControl. It displays a DataTemplate for each "job" in Jobs. A job can be started/stopped by clicking the buttons in the DataTemplate. The DataTemplate also contains the Status of a job (started/stopped) but this is not updated when in trigger the PropertyChanged?.Invoke(...) event from the JobViewModel.
When i move the PropertyChanged?.Invoke(...) to the (abstract) RJob class (for a specific variable in that class), it works fine thought this does not feel nice to me. I don't want to add "view logic" to my classes to update a view. I want to keep this logic in the ViewModels.
I also want to avoid that i need to do a PropertyChanged?.Invoke(...) for each variable in a class (RJob in this example) that i show in a view. I prefer to do something like PropertyChanged?.Invoke(job) so i automatically updates all showed variables of the concerning job.
I have 2 questions about this:
- What is de convinient place to trigger the 
PropertyChanged?.Invoke(...)? - Is it possible to trigger the 
PropertyChanged?.Invoke(...)on object level instead of on property level so it updates all binded properties without creating aPropertyChanged?.Invoke(...)for each individual property? 
Note that i cleaned up the code a bit for this question.
My JobView.xaml:
<UserControl x:Class="ECRO.MVVM.View.JobView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:viewModel="clr-namespace:ECRO.MVVM.ViewModel"
             mc:Ignorable="d" d:DesignWidth="800" Height="780">
    <UserControl.DataContext>
        <viewModel:JobViewModel/>
    </UserControl.DataContext>
    <UserControl.Resources>
            <DataTemplate x:Key="jobTemplate">
                <Grid Height="90" Width="760">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="677*"/>
                        <ColumnDefinition Width="123*"/>
                    </Grid.ColumnDefinitions>
                    <Border CornerRadius="10"
                    Background="#353340"
                    Height="80"
                    Margin="10,0,10,0"
                    VerticalAlignment="Center" Grid.ColumnSpan="2">
                        <Grid>
                            ...
                        <StackPanel Orientation="Vertical"
                                    Margin="5 2 0 0">
                            <Label Content="Naam" FontWeight="Bold" Style="{StaticResource DefaultLabel}" Margin="0 -5 0 -5"/>
                            <Label Content="Status" FontWeight="Bold" Style="{StaticResource DefaultLabel}" Margin="0 -5 0 -5"/>
                        </StackPanel>
                        <StackPanel Grid.Column="1" Margin="2">
                            <Label Content="{Binding Name}" Style="{StaticResource DefaultLabel}" Margin="0 -5 0 -5"/>
                            <Label Content="{Binding Status}"  Style="{StaticResource DefaultLabel}" Margin="0 -5 0 -5"/>
                        </StackPanel>
                            <Border Grid.RowSpan="2"
                                    Grid.Column="2"
                                    Grid.Row="0"
                                    HorizontalAlignment="Right"
                                    Margin="0 0 20 0">
                                <StackPanel Orientation="Horizontal">
                                    <Button Content="Start" 
                                        Style="{StaticResource DarkButton}" 
                                        Command="{Binding DataContext.StartJobCmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                        CommandParameter="{Binding}"/>
                                    <Button Content="Stop" 
                                        Style="{StaticResource DarkButton}" 
                                        Command="{Binding DataContext.StopJobCmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                        CommandParameter="{Binding}"/>
                                   ...
                                </StackPanel>
                            </Border>
                        </Grid>
                    </Border>
                </Grid>
            </DataTemplate>
    </UserControl.Resources>
    <StackPanel>
        <TextBlock Text="Jobs" Style="{StaticResource HeaderTextBlock}"/>
        <ScrollViewer Width="780" Height="700">
            <ItemsControl ItemsSource="{Binding Jobs}" 
                          ItemTemplate="{StaticResource jobTemplate}"/>
        </ScrollViewer>
    </StackPanel>
</UserControl>
Not working JobViewModel with RJob:
My JobViewModel
using ECRO.Class;
using ECRO.Core;
using ECRO.RJobs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
namespace ECRO.MVVM.ViewModel
{
    class JobViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<RJob> _jobs;
        public ObservableCollection<RJob> Jobs { get => _jobs; set { _jobs = value; } }
        public event PropertyChangedEventHandler? PropertyChanged;
        public RelayCommand StartJobCmd { get; set; }
        public RelayCommand StopJobCmd { get; set; }
        ...
        public JobViewModel()
        {
            _jobs = Engine.Instance.RJobs;
            StartJobCmd = new RelayCommand(o => { 
                RJob job = (RJob)o;
                job.Start();
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(job)));
            });
            StopJobCmd = new RelayCommand(o => {
                RJob job = (RJob)o;
                job.Stop();
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(job)));
            });
            ...
        }
    }
}
My abstract RJob which is inherrited by all jobs:
...
namespace ECRO.Class
{
    public abstract class RJob
    {
        private JobStatus jobStatus;
        public JobStatus Status { get => this.jobStatus; }
        ...
        public JobStatus Start()
        {
            if (active && jobStatus != JobStatus.Running)
            {
                try {
                    runTask();
                    jobStatus = JobStatus.Running;
                } catch (Exception e) {
                    this.jobStatus = JobStatus.Failed;
                    Log.Error($"Some error occurred in job \"{Name}\", check the job log for more details.");
                    Log.Error(e, this);
                }
            }
            SaveConfiguration();
            return jobStatus;
        }
        public JobStatus Stop(bool saveConfig = true)
        {
            if (jobStatus == JobStatus.Running)
            {
                jobExcecuteTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
                jobStatus = JobStatus.Stopped;
            }
            if (saveConfig)
            {
                SaveConfiguration();
            }
            return jobStatus;
        }
        ...
    }
}
Working JobViewModel & RJob:
My JobViewModel
using ECRO.Class;
using ECRO.Core;
using ECRO.RJobs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
namespace ECRO.MVVM.ViewModel
{
    class JobViewModel 
    {
        private ObservableCollection<RJob> _jobs;
        public ObservableCollection<RJob> Jobs { get => _jobs; set { _jobs = value; } }
        public RelayCommand StartJobCmd { get; set; }
        public RelayCommand StopJobCmd { get; set; }
        ...
        public JobViewModel()
        {
            _jobs = Engine.Instance.RJobs;
            StartJobCmd = new RelayCommand(o => { 
                ((RJob)o).Start();
            });
            StopJobCmd = new RelayCommand(o => {
                ((RJob)o).Stop();
            });
            ...
        }
    }
}
My abstract RJob which is inherrited by all jobs:
...
namespace ECRO.Class
{
    public abstract class RJob : INotifyPropertyChanged
    {
        private JobStatus jobStatus;
        public JobStatus Status { get => this.jobStatus; }
        
        ...
        public JobStatus Start()
        {
            if (active && jobStatus != JobStatus.Running)
            {
                try {
                    runTask();
                    jobStatus = JobStatus.Running;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Status"));
                } catch (Exception e) {
                    this.jobStatus = JobStatus.Failed;
                    Log.Error($"Some error occurred in job \"{Name}\", check the job log for more details.");
                    Log.Error(e, this);
                }
            }
            SaveConfiguration();
            return jobStatus;
        }
        public JobStatus Stop(bool saveConfig = true)
        {
            if (jobStatus == JobStatus.Running)
            {
                jobExcecuteTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
                jobStatus = JobStatus.Stopped;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Status"));   
            }
            if (saveConfig)
            {
                SaveConfiguration();
            }
            return jobStatus;
        }
        
        ...
    }
}