0

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.

Obbles
  • 533
  • 3
  • 17
  • 1
    The second Binding doesn't have an explicitly set source object and hence uses the current DataContext. However, you've assigned `DataContext = this` in the UserControl's constructor, which breaks the Binding. You should never explicitly set the DataContext of a UserControl, neither in code nor in XAML, neither to `this` nor to a "private" view model class. – Clemens Jun 05 '18 at 05:12
  • Thank you for catching the datacontext problem. I missed it. (Not sure what I was thinking having that there). I have removed it. Experimenting with explicitly set source now. Will return once I have worked through thihngs. – Obbles Jun 05 '18 at 10:29

1 Answers1

0

How does your Entity.MarketDescription.SuspendTime class look like ?

The property SuspendTime should implement interface INotifyPropertyChanged to notify your control everytime it updates.

By the way, I notice that you don't have Mode=OneTime in your working code. Is there any reason for that ?

Here's an example on implementing INPC

Something like:

public class MarketDescription : INotifyPropertyChanged
{
    // implements your interface here
    ...
    // your property to bind

    private DateTime suspendTime;
    public DateTime SuspendTime
    {
        get { return suspendTime; }
        set 
        {
            suspendTime = value;
            NotifyPropertyChanged("SuspendTime");
        }
    }
}
Nam Le
  • 568
  • 5
  • 12
  • The Entity.MarketDescription.SuspendTime is a field in an Entity Framework 6.0 Code first entity. I have tested that INotifyPropertyChanged is working. (My code uses DevExpress MVVM framework, which implements that for me, so it's a little complicated to demonstrate). It does successfully update using regular controls placed on the same form. – Obbles Jun 05 '18 at 10:36