为什么 DataTrigger 使用不正确的 DataContext?

问题描述 投票:0回答:1

我的项目如下所示: 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;
    }
}

输出值转换器.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>

在 MainWindow.xaml 行中

<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">

IDE 报告 DataContext 指定不正确并写入: 无法解析“WpfAppForExample.ViewModels.MainViewModel”类型的数据上下文中的属性“TheDifferenceBetweenThePlannedAndActualEndOfTask”。

但是,一切正常。这是为什么?

我尝试了以下 DataTrigger Binding 调整,但它们都不起作用:

  1. 绑定RelativeSource={RelativeSource Self}
  2. 绑定TaskClassList[this].TheDifferenceBetweenThePlannedAndActualEndOfTask
  3. xmlns:taskClassDirectory="clr-namespace:Library.TaskClassDirectory; assembly=Library" 绑定RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type taskClassDirectory:TaskClass}}
c# wpf data-binding datacontext datatrigger
1个回答
0
投票

IDE(XAML 设计器)不够聪明,无法断定 DataContext 实际上是一个 TaskClass 对象。 你可以忽略这个。

如果您对警告不满意:可以选择使用

d:DataContext
属性提供仅供设计器使用的 DataContext。这仅由设计器评估并在运行时被忽略。

示例:

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

另请参阅此处:我需要什么来进一步限定 DataContext 的绑定?

© www.soinside.com 2019 - 2024. All rights reserved.