I am attempting to make a WPF user control that counts down to a specificied date and turns the text red to alert the user when there is a specified timespan left.
My code works as planned when I pass in static parameters. My code fails when I pass in bound parameters.
Here is my calling code;
<!-- WORKS (STATIC PARAMETERS) -->
<controls:TextEditDateCountDown
AlertAtMinutesLeft="2100"
DateStampCountDownTo="{Binding Source={x:Static system:DateTime.Now},StringFormat='HH:mm:ss tt'}" />
<!-- DOES NOT WORK (BOUND PARAMETERS) -->
<controls:TextEditDateCountDown
AlertAtMinutesLeft="2100"
DateStampCountDownTo="{Binding Entity.MarketDescription.SuspendTime,Mode=OneTime}"/>
<!-- DOES NOT WORKING (BOUND PARAMETERS TRYING PRIORITY BINDING AS AT http://www.blackwasp.co.uk/WPFPriorityBinding.aspx) -->
<controls:TextEditDateCountDown
AlertAtMinutesLeft="2100">
<controls:TextEditDateCountDown.DateStampCountDownTo>
<PriorityBinding>
<Binding Path="Entity.MarketDescription.SuspendTime" Mode="OneTime" IsAsync="True"/>
<Binding Path="Entity.MarketId" Mode="OneTime" IsAsync="True"/>
</PriorityBinding>
</controls:TextEditDateCountDown.DateStampCountDownTo>
</controls:TextEditDateCountDown>
The control code looks as follows;
<!-- THE XAML -->
<Grid>
<dxe:TextEdit
x:Name="myTextEdit"
IsPrintingMode="True"
Mask="dd\d HH\h mm\m ss\s"
MaskType="DateTimeAdvancingCaret"
MaskUseAsDisplayFormat="True">
<dxe:TextEdit.Style>
<Style TargetType="dxe:TextEdit">
<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding IsAlertOn, RelativeSource={RelativeSource AncestorType=controls:TextEditDateCountDown},Mode=OneTime}">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
<DataTrigger Value="False" Binding="{Binding IsAlertOn, RelativeSource={RelativeSource AncestorType=controls:TextEditDateCountDown},Mode=OneTime}"> <Setter Property="Foreground" Value="Black" />
<Setter Property="FontWeight" Value="Regular" />
</DataTrigger>
</Style.Triggers>
</Style>
</dxe:TextEdit.Style>
</dxe:TextEdit>
</Grid>
<!-- THE CODE BEHIND -->
public partial class TextEditDateCountDown : UserControl {
public TextEditDateCountDown() {
InitializeComponent();
timer.Interval = new TimeSpan(0, 0, 1);
timer.Tick += Timer_Tick;
timer.Start();
}
DispatcherTimer timer = new DispatcherTimer();
public static readonly DependencyProperty DateStampCountDownToProperty = DependencyProperty.Register("DateStampCountDownTo", typeof(DateTime), typeof(TextEditDateCountDown), new PropertyMetadata(DateTime.MinValue, CountDownToChanged));
public static readonly DependencyProperty AlertAtMinutesLeftProperty = DependencyProperty.Register("AlertAtMinutesLeft", typeof(string), typeof(TextEditDateCountDown), new PropertyMetadata(string.Empty, Changed));
public static readonly DependencyProperty IsAlertOnProperty = DependencyProperty.Register("IsAlertOn", typeof(bool), typeof(TextEditDateCountDown), new PropertyMetadata(false));
public DateTime? DateStampCountDownTo {
get {
return (DateTime?)GetValue(DateStampCountDownToProperty);
}
set { SetValue(DateStampCountDownToProperty, value); }
}
public virtual string AlertAtMinutesLeft {
get {
return (string)GetValue(AlertAtMinutesLeftProperty);
}
set { SetValue(AlertAtMinutesLeftProperty, value); }
}
public bool IsAlertOn {
get { return (bool)GetValue(IsAlertOnProperty); }
set { SetValue(IsAlertOnProperty, value); }
}
private bool UpdateIsAlertOn(string value) {
if (value != null && value != "") {
int i = int.Parse(AlertAtMinutesLeft);
return DateTime.Now > DateStampCountDownTo.Value.AddMinutes(i);
} else
return false;
}
private DateTime? UpdateDateStampCountDownTo(DateTime? value) {
return DateStampCountDownTo.Value;
}
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var textEditControl = d as TextEditDateCountDown;
if (textEditControl == null) return;
textEditControl.IsAlertOn = textEditControl.UpdateIsAlertOn(textEditControl.AlertAtMinutesLeft);
}
private static void CountDownToChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var textEditControl = d as TextEditDateCountDown;
if (textEditControl == null) return;
textEditControl.DateStampCountDownTo = textEditControl.UpdateDateStampCountDownTo(textEditControl.DateStampCountDownTo);
}
void Timer_Tick(object sender, EventArgs e) {
if (DateStampCountDownTo != null) {
TimeSpan t = DateStampCountDownTo.Value - TimeProvider.Current.UtcNow;
myTextEdit.MaskType = MaskType.None;
if (t < TimeSpan.Zero) {
myTextEdit.MaskType = MaskType.None;
myTextEdit.EditValue = "Closed";
}
myTextEdit.EditValue = t;
} else {
myTextEdit.EditValue = null;
}
}
}
This is a complex problem (I hope not complicated by using DevExpress' Textedit control). I was wondering if anyone give me any pointers on where I should look to go about fixing it? (or how to fix it). Thank you heaps for your time.
UPDATE1: I do not know why, But; This event handler never runs -> private static void CountDownToChanged(DependencyObj... This event handler does -> private static void Changed(DependencyObj...
Could the problem be in the DependencyProperty declaration?
UPDATE2: Further to update1, I have also found that the CountDownToChanged event handler runs using a static parameter input, but not when using the bound parameter.
UPDATE3: I have updated the code here to remove the datacontext = this code. And also the corrected the relative binding in the control's xaml.
Still problem persists.
UPDATE4: Ok. It is working. After hours of trying a gazillion things, I honestly cannot figure out what I did to make it work. I suspect that Clemens linked duplicate was the answer and I had made some form of binding error that obscured the fact that it did work.
For that reason it is probably worth deleting this post as I do not know the real solution and I suspect no one will find this useful.