Zhan
Zhan

Reputation: 13

Why is DataTrigger working with an incorrect DataContext?

My project looks like this: enter image description here

TaskClass.cs:

namespace Library.TaskClassDirectory;

public sealed class TaskClass : INotifyPropertyChanged
{
    public TaskClass(
        TimeSpan plannedLaborCost,
        TimeSpan actualLaborCost)
    {
        Labor = new Labor(plannedLaborCost, actualLaborCost, this);
    }

    private Labor _labor = null!;
    public Labor Labor
    {
        get => _labor;
        set
        {
            if (Equals(value, _labor))
            {
                return;
            }

            _labor = value;
            OnPropertyChanged();
        }
    }
    private TimeSpan _theDifferenceBetweenThePlannedAndActualEndOfTask;
    public TimeSpan TheDifferenceBetweenThePlannedAndActualEndOfTask
    {
        get => _theDifferenceBetweenThePlannedAndActualEndOfTask;
        set
        {
            if (value.Equals(_theDifferenceBetweenThePlannedAndActualEndOfTask))
            {
                return;
            }

            _theDifferenceBetweenThePlannedAndActualEndOfTask = value;
            OnPropertyChanged();
        }
    }

    internal void TheDifferenceBetween()
    {
        TheDifferenceBetweenThePlannedAndActualEndOfTask = Labor.PlannedCost.Subtract(
            Labor.ActualCost
        );

        OnPropertyChanged(nameof(TheDifferenceBetweenThePlannedAndActualEndOfTask));
    }

    public event PropertyChangedEventHandler? PropertyChanged;

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

    private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
        {
            return false;
        }

        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

Labor.cs:

namespace Library.TaskClassDirectory;

public class Labor: INotifyPropertyChanged
{
    public Labor(TimeSpan plannedCost, TimeSpan actualCost, TaskClass taskClass)
    {
        PlannedCost = plannedCost;
        ActualCost = actualCost;
        TaskClass = taskClass;
    }

    private TimeSpan _plannedCost;
    public TimeSpan PlannedCost
    {
        get => _plannedCost;
        set
        {
            if (value.Equals(_plannedCost))
            {
                return;
            }

            if (SetField(ref _plannedCost, value))
            {
                TaskClass?.TheDifferenceBetween();
            }
            _plannedCost = value;
            OnPropertyChanged();
        }
    }

    private TimeSpan _actualCost;
    public TimeSpan ActualCost
    {
        get => _actualCost;
        set
        {
            if (value.Equals(_actualCost))
            {
                return;
            }

            if (SetField(ref _actualCost, value))
            {
                TaskClass?.TheDifferenceBetween();
            }

            _actualCost = value;
            OnPropertyChanged();
        }
    }

    private TaskClass? _taskClass;
    public TaskClass? TaskClass
    {
        get => _taskClass;
        set
        {
            if (Equals(value, _taskClass))
            {
                return;
            }

            _taskClass = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

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

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

MainViewModel.cs:

namespace WpfAppForExample.ViewModels;

public class MainViewModel:INotifyPropertyChanged
{
    public ObservableCollection<TaskClass> TaskClassList { get; set; }

    public MainViewModel()
    {
        TaskClassList = new ObservableCollection<TaskClass>();
        GetTaskClassList();

    }

    private void GetTaskClassList()
    {
        TaskClass taskClass = new TaskClass(TimeSpan.Zero, TimeSpan.Zero);

        TaskClassList.Clear();
        TaskClassList.Add(taskClass);
    }

    public event PropertyChangedEventHandler? PropertyChanged;

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

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
        {
            return false;
        }

        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

OutputValueConverter.cs:

namespace WpfAppForExample.ViewModels;

public class OutputValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is TimeSpan val && parameter != null && parameter.ToString()!.Contains("IsColor"))
        {
            if (val > TimeSpan.Zero)
            {
                return "Red";
            }
            else if (val < TimeSpan.Zero)
            {
                return "Green";
            }
            else
            {
                return "Black";
            }
        }

        return value;
    }

    public object ConvertBack(object value, Type targetType, object? parameter, CultureInfo culture) =>
        value;
}

MainWindow.xaml.cs:

namespace WpfAppForExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

MainWindow.xaml:

<Window x:Class="WpfAppForExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfAppForExample"
        xmlns:viewModels="clr-namespace:WpfAppForExample.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <viewModels:MainViewModel></viewModels:MainViewModel>
    </Window.DataContext>
    <Window.Resources>
        <viewModels:OutputValueConverter x:Key="OutputValueConverter"></viewModels:OutputValueConverter>
    </Window.Resources>
    <Grid>
        <DataGrid Height="780"
                  Width="auto"
                  ItemsSource="{Binding TaskClassList}"
                  AutoGenerateColumns="False"
                  HorizontalGridLinesBrush="DarkGray"
                  RowBackground="LightGray"
                  AlternatingRowBackground="White">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Трудозатраты планируемые"
                                    Binding="{Binding Labor.PlannedCost,
                                            Converter={StaticResource OutputValueConverter}}"
                                    IsReadOnly="False"/>
                <DataGridTextColumn Header="Трудозатраты фактические"
                                    Binding="{Binding Labor.ActualCost,
                                            Converter={StaticResource OutputValueConverter}}"
                                    IsReadOnly="False"/>
                <DataGridTextColumn Header="Сравнение планируемого и фактического окончания работ"
                                    Binding="{Binding TheDifferenceBetweenThePlannedAndActualEndOfTask,
                                    Converter={StaticResource OutputValueConverter}}"
                                    IsReadOnly="True">
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="TextBlock">
                                        <Setter Property="Foreground" Value="Black">
                                        </Setter>
                                        <Style.Triggers>
                                            <DataTrigger Binding="{
                                            Binding TheDifferenceBetweenThePlannedAndActualEndOfTask,
                                                Converter={StaticResource OutputValueConverter},
                                                ConverterParameter=IsColor}"
                                                         Value="Black">
                                                <Setter Property="Foreground" Value="Black"></Setter>
                                            </DataTrigger>
                                            <DataTrigger Binding="{
                                            Binding TheDifferenceBetweenThePlannedAndActualEndOfTask,
                                                Converter={StaticResource OutputValueConverter},
                                                ConverterParameter=IsColor}"
                                                         Value="Green">
                                                <Setter Property="Foreground" Value="Green"></Setter>
                                            </DataTrigger>
                                            <DataTrigger Binding="{
                                            Binding TheDifferenceBetweenThePlannedAndActualEndOfTask,
                                                Converter={StaticResource OutputValueConverter},
                                                ConverterParameter=IsColor}"
                                                         Value="Red">
                                                <Setter Property="Foreground" Value="Red"></Setter>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

In the MainWindow.xaml lines

<DataTrigger Binding="{Binding TheDifferenceBetweenThePlannedAndActualEndOfTask, Converter={StaticResource OutputValueConverter}, ConverterParameter=IsColor}" Value="Black">
<DataTrigger Binding="{Binding TheDifferenceBetweenThePlannedAndActualEndOfTask, Converter={StaticResource OutputValueConverter}, ConverterParameter=IsColor}" Value="Green">
<DataTrigger Binding="{Binding TheDifferenceBetweenThePlannedAndActualEndOfTask, Converter={StaticResource OutputValueConverter}, ConverterParameter=IsColor}" Value="Red">

The IDE reports that the DataContext is specified incorrectly and writes: Unable to resolve property 'TheDifferenceBetweenThePlannedAndActualEndOfTask' in data context of type 'WpfAppForExample.ViewModels.MainViewModel'.

However, everything works correctly. Why is that?

I've tried the following DataTrigger Binding adjustments, but they all don't work:

  1. Binding RelativeSource={RelativeSource Self}
  2. Binding TaskClassList[this].TheDifferenceBetweenThePlannedAndActualEndOfTask
  3. xmlns:taskClassDirectory="clr-namespace:Library.TaskClassDirectory;assembly=Library" Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type taskClassDirectory:TaskClass}}

Upvotes: 0

Views: 56

Answers (1)

Klaus G&#252;tter
Klaus G&#252;tter

Reputation: 11977

The IDE (XAML designer) is not smart enough to conclude that the DataContext is in fact a TaskClass object. You can just ignore this.

If you are unhappy with the warning: there is the option to provide a DataContext just for use by the Designer using the d:DataContext attribute. This is evaluated only by the Designer and ignored at runtime.

Example:

<Style TargetType="TextBlock" d:DataContext="{d:DesignInstance viewModels:TaskClass}">

See also here: What do I need to further qualify the DataContext for a binding?

Upvotes: 0

Related Questions