GrantA
GrantA

Reputation: 560

Expression Interactivity Data Trigger Does Not Correctly Display Bound Value Originally

I have an Expression Interactivity DataTrigger that changes the Text property of a TextBlock based on a bound TimeSpan property. When that value is greater-than-or-equal-to Timespan.Zero, the text will be the value of the property. When the value is less than zero, the value changes to "??:??:??".

The relevant code is as follows:

<i:Interaction.Triggers>
    <ei:DataTrigger Binding="{Binding InspectionService.TimeRemainingForPart}" Comparison="GreaterThanOrEqual" Value="{x:Static sys:TimeSpan.Zero}">
        <ei:ChangePropertyAction PropertyName="Text" Value="{Binding InspectionService.TimeRemainingForPart}" />
    </ei:DataTrigger>

    <ei:DataTrigger Binding="{Binding InspectionService.TimeRemainingForPart}" Comparison="LessThan" Value="{x:Static sys:TimeSpan.Zero}">
        <ei:ChangePropertyAction PropertyName="Text" Value="??:??:??" />
    </ei:DataTrigger>
</i:Interaction.Triggers>

The TimeRemainingForPart property is updated via a timer in the InspectionService. When the timer is running, everything is fine. When the timer is stopped (which will set the TimeRemainingForPart to Timespan.Zero), the View shows "00:00:00" as expected. However, when the application first loads, there is nothing displayed in the text block. I've even tried changing the value/notifying the property changing from the constructor of InspectionService and nothing happens.

I can always just go to a standard WPF DataTrigger with a converter called "TimespanLessThanZeroConverter" or somesuch, but any idea as to why the current way I'm doing it doesn't work when the application starts-up?

Edit: Forgot to mention I had tried calling OnPropertyChanged on the TimeRemainingForPart property in my constructor for the service just in case something wasn't getting notified correctly, but that didn't seem to accomplish anything.

Edit2: Adding full XAML for Textblock and the relevant sections of the ViewModel and Service.

XAML:

<TextBlock Grid.Row="1" FontSize="56" FontWeight="Bold" Text="{Binding InspectionService.TimeRemainingForPart}">
    <TextBlock.Style>
        <Style TargetType="TextBlock" BasedOn="{StaticResource StatusIndicatorTextBlockStyle}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding InspectionService.InspectionExecutionState}" Value="{x:Static enum:InspectionExecutionStates.Paused}">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever">
                                <ColorAnimation Storyboard.TargetProperty="Foreground.(SolidColorBrush.Color)" To="Transparent" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>

                    <DataTrigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard Duration="0:0:1">
                                <ColorAnimation Storyboard.TargetProperty="Foreground.(SolidColorBrush.Color)" To="White" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>

    <i:Interaction.Triggers>
        <ei:DataTrigger Binding="{Binding InspectionService.TimeRemainingForPart}" Comparison="GreaterThanOrEqual" Value="{x:Static sys:TimeSpan.Zero}">
            <ei:ChangePropertyAction PropertyName="Text" Value="{Binding InspectionService.TimeRemainingForPart}" />
        </ei:DataTrigger>

        <ei:DataTrigger Binding="{Binding InspectionService.TimeRemainingForPart}" Comparison="LessThan" Value="{x:Static sys:TimeSpan.Zero}">
            <ei:ChangePropertyAction PropertyName="Text" Value="??:??:??" />
        </ei:DataTrigger>
    </i:Interaction.Triggers>
</TextBlock>

ViewModel:

public class MyViewModel : IMyViewModel
{
    public IInspectionService InspectionService { get; private set; }

    public MyViewModel (IInspectionService inspectionService)
    {
        this.InspectionService = inspectionService;
    }
}

Service:

public class InspectionService : BindableBase, IInspectionService
{
    private readonly IModeService _modeService;
    private readonly IRemainingTimeFileServiceFactory _remainingTimeFileServiceFactory;

    private string _inspectionCell;

    private readonly DispatcherTimer _timeRemainingForPartTimer;

    #region IInspectionService Members

    private InspectionExecutionStates _inspectionExecutionState;
    public InspectionExecutionStates InspectionExecutionState
    {
        get { return this._inspectionExecutionState; }
        private set { this.SetProperty(ref this._inspectionExecutionState, value); }

    private string _inspectionName;
    public string InspectionName
    {
        get { return this._inspectionName; }
        private set { this.SetProperty(ref this._inspectionName, value); }
    }

    private TimeSpan _timeRemainingForPart;
    public TimeSpan TimeRemainingForPart
    {
        get { return this._timeRemainingForPart; }
        private set { this.SetProperty(ref this._timeRemainingForPart, value); }

    private TimeSpan _totalTimeRemaining;
    public TimeSpan TotalTimeRemaining
    {
        get { return this._totalTimeRemaining; }
        private set { this.SetProperty(ref this._totalTimeRemaining, value); }
    }

    #endregion

    public InspectionService(IModeService modeService, IRemainingTimeFileServiceFactory remainingTimeFileServiceFactory)
    {
        this._modeService = modeService;
        this._remainingTimeFileServiceFactory = remainingTimeFileServiceFactory;

        this._timeRemainingForPartTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
        this._timeRemainingForPartTimer.Tick += this.TimeRemainingForPartTimerOnTick;
    }

    private void StartSelectedInspection(InspectionPlanInfo inspectionPlanInfo)
    {
        this.SetInspectionProperties(inspectionPlanInfo);
        this.StartInspection
    }

    #endregion

    #region Private Methods

    private void StartInspection()
    {
        this.TotalTimeRemaining = this._remainingTimeFileServiceFactory.GetIRemainingTimeFileService(this._inspectionCell).GetInspectionTime(this.InspectionName);
        this.TimeRemainingForPart = this._modeService.IsStudyActive ? TimeSpan.MinValue : this.TotalTimeRemaining;
        this._timeRemainingForPartTimer.Start();
    }

    private void StopInspection()
    {
        this.ClearInspectionProperties();
        this._timeRemainingForPartTimer.Stop();
    }

    private void SetInspectionProperties(InspectionPlanInfo inspectionPlanInfo)
    {
        this.InspectionName = inspectionPlanInfo.InspectionName;
        this._inspectionCell = inspectionPlanInfo.Cell;
    }

    private void ClearInspectionProperties()
    {
        this.InspectionName = "";
        this.TimeRemainingForPart = TimeSpan.Zero;
        this.TotalTimeRemaining = TimeSpan.Zero;
    }

    private void TimeRemainingForPartTimerOnTick(object sender, EventArgs eventArgs)
    {
        if (this.TimeRemainingForPart < TimeSpan.Zero)
        {
            this._timeRemainingForPartTimer.Stop();
        }
        else
        {
            this.TimeRemainingForPart -= TimeSpan.FromSeconds(1);
            this.TotalTimeRemaining -= TimeSpan.FromSeconds(1);
        }
    }
}

Edit 3:

So apparently it can't be done without a converter so the TextBlock code is modified as follows:

<TextBlock Grid.Row="1" FontSize="56" FontWeight="Bold">
    <TextBlock.Style>
        <Style TargetType="TextBlock" BasedOn="{StaticResource StatusIndicatorTextBlockStyle}">
            <Setter Property="Text" Value="{Binding InspectionService.TimeRemainingForPart}" />

            <Style.Triggers>
                <DataTrigger Binding="{Binding InspectionService.TimeRemainingForPart, Converter={StaticResource TimespanLessThanZeroConverter}}"
                        Value="True">
                    <Setter Property="Text" Value="??:??:??" />
                </DataTrigger>

                <DataTrigger Binding="{Binding InspectionService.InspectionExecutionState}" Value="{x:Static enum:InspectionExecutionStates.Paused}">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever">
                                <ColorAnimation Storyboard.TargetProperty="Foreground.(SolidColorBrush.Color)" To="Transparent" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>

                    <DataTrigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard Duration="0:0:1">
                                <ColorAnimation Storyboard.TargetProperty="Foreground.(SolidColorBrush.Color)" To="White" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>
</TextBlock>

And for completeness/posterity sake the converter looks like this:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace CurrentInspection.Converters
{
    public class TimespanLessThanZeroConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is TimeSpan))
            {
                return false;
            }

            return (TimeSpan)value < TimeSpan.Zero;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue;
        }
    }
}

Upvotes: 1

Views: 1165

Answers (1)

mm8
mm8

Reputation: 169400

Are you binding the Text property of the TextBox to the source property like this?:

<TextBlock Text="{Binding InspectionService.TimeRemainingForPart}">
    <i:Interaction.Triggers>
        <ei:DataTrigger Binding="{Binding InspectionService.TimeRemainingForPart}" Comparison="LessThan" Value="{x:Static sys:TimeSpan.Zero}">
            <ei:ChangePropertyAction PropertyName="Text" Value="??:??:??" />
        </ei:DataTrigger>
    </i:Interaction.Triggers>
</TextBlock>

Then it should just be a matter of setting the TimeRemainingForPart property to the default value in the constructor of the view model.

As you have noticed I have removed the first interaction trigger. You better bind the Text property to the source property and use a converter instead of using two ChangePropertyActions. Or bind to a string property of the view model that returns the correct string based on the TimeSpan value.

Upvotes: 1

Related Questions