0

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}}
9
  • It's just the XAML Designer complaining. That happens because it's all but perfect. There is no "incorrect DataContext". Commented Oct 24, 2024 at 8:56
  • The IDE is not smart enough to conclude that the DataContext is in fact a TaskClass object. You can just ignore this. Or apply a d:DataContext attribute to convince the IDE. Commented Oct 24, 2024 at 8:56
  • @KlausGütter How do I apply this attribute? I've never done anything like this. Commented Oct 24, 2024 at 9:35
  • d:DataContext="{d:DesignInstance viewModels:TaskClass}" Commented Oct 24, 2024 at 9:43
  • @KlausGütter You are a wizard! I spent 3 days solving the issue, but it was solved in half an hour. Could you provide links to read about this approach? Commented Oct 24, 2024 at 9:48

1 Answer 1

0

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?

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.