I would like to display in a TextBlock
my variable TrainDelay
in a specific format
I used a Converter IntToTimeSpanConverter
to format TrainDelay : (mm:ss)
so according to the value of TrainDelay such as:
Delayed (00:23)
in red colorOn Time (00:00)
in dark colorIn Advance (- 00:15)
in green color Some code:
public class TimeSpanFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int.TryParse(value.ToString(), out int time);
value = TimeSpan.FromSeconds(time);
if (string.IsNullOrWhiteSpace(value.ToString()) || ((TimeSpan)value).Equals(TimeSpan.MinValue))
return "––:––";
else if(time > 0)
{
return TrainDelay.Delayed + " " + ((((TimeSpan)value) < TimeSpan.Zero) ? "-" : "") + ((TimeSpan)value).ToString(@"mm\:ss");
}
else if (time < 0)
{
return TrainDelay.InAdvance + " " + ((((TimeSpan)value) < TimeSpan.Zero) ? "-" : "") + ((TimeSpan)value).ToString(@"mm\:ss");
}
else
{
return TrainDelay.OnTime + " " + ((((TimeSpan)value) < TimeSpan.Zero) ? "-" : "") + ((TimeSpan)value).ToString(@"mm\:ss");
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum TrainDelay
{
OnTime,
Delayed,
InAdvance
}
I have tried this using DataTrigger
in this XAML :
<TextBlock Name="tb" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="defaultDelay"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=tb, Path=TrainDelay, Converter={StaticResource TimeSpanFormatConverter}}" Value="Delayed">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Text" Value="{Binding TrainDelay, Converter={StaticResource TimeSpanFormatConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I'm still not having the right result !
I'm Beginner in C# WPF programming , I need help to implement this or maybe have more explanations to really understand the problem
What this Binding does, is it goes and finds the element named tb
, and looks on that element for a property named TrainDelay
. Right there it fails, because TextBlock has no property by that name. TrainDelay
is a property of a viewmodel, not of the control.
<DataTrigger
Binding="{Binding ElementName=tb, Path=TrainDelay, Converter={StaticResource TimeSpanFormatConverter}}"
Value="Delayed">
If you want to trigger on the "delayedness" of the train, you'll need another converter to convert the TrainDelay
property into just the enum. Comparing "Delayed" to your formatted time string will never work.
That new converter can look like this. It's just a simplified version of the other one. While I'm at it, I'm going to rewrite your converter to simplify it and remove a lot of redundant code. Redundant code is a bad idea: At first, it's just clutter. Then somebody comes along to maintain the thing a year later, and they waste some time confirming that those three subexpressions are really identical after all. Next year somebody else changes two of them. A year after that, somebody needs to make another change and mistakenly assumes the third one was supposed to be different. Meanwhile, somebody else copied and pasted the whole mess, and now you've got more problems.
public class TimeToDelayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int.TryParse(value.ToString(), out int time);
var span = TimeSpan.FromSeconds(time);
if (string.IsNullOrWhiteSpace(value.ToString()) || span.Equals(TimeSpan.MinValue))
{
return null;
}
else
{
return TimeSpanFormatConverter.SecondsToDelay(time);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class TimeSpanFormatConverter : IValueConverter
{
public static TrainDelay SecondsToDelay(int time)
{
if (time > 0)
{
return TrainDelay.Delayed;
}
else if (time < 0)
{
return TrainDelay.InAdvance;
}
else
{
return TrainDelay.OnTime;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int.TryParse(value.ToString(), out int time);
// Don't assign this back to value. Assign it to a new variable that's properly
// typed, so you don't need to cast it.
var span = TimeSpan.FromSeconds(time);
if (string.IsNullOrWhiteSpace(value.ToString()) || span.Equals(TimeSpan.MinValue))
{
return "––:––";
}
else
{
// No need to copy and paste the same code three times.
var timeStr = ((span < TimeSpan.Zero) ? "-" : "") + span.ToString(@"mm\:ss");
return $"{SecondsToDelay(time)} {timeStr}";
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum TrainDelay
{
OnTime,
Delayed,
InAdvance
}
And then your trigger looks like this. Note that it's using a different converter than before.
<DataTrigger
Binding="{Binding TrainDelay, Converter={StaticResource TimeToDelayConverter}}"
Value="Delayed">