使用 C# 和 XAML 的 UWP 多个倒计时

UWP multiple Countdowns with C# and XAML

我正在使用 C# 和 XAML 构建一个 UWP 应用程序。在自定义用户控件上,我构建了一个 "list" 更好的 ListView,其中包含 DataTemplate 和内部网格。 在这个网格中,我有多个带有数据绑定的 TextBlock。在每个生成的行中,我现在想要一个从 DateTimeOffset.Now 到未来(或过去 - 负值)的特定 DateTimeOffset 的倒数计时器。

我尝试用文本 属性 构建一个 class CountDownElement,并用该字符串调用我的 "countdown" Class。在 Xaml 中,我在 TextBlock 中对该字符串进行了数据绑定。 但它并没有改变。我认为 tghe UI 没有更新?有没有不同的方法 - 也许只用 XAML 构建倒计时并将其值绑定到 ViewModel 的生成值?

感谢您的帮助!

这是我的倒计时Class:

 public class Countdown
{
    public DispatcherTimer DispatcherTimer;
    public DateTimeOffset StartTime;
    private TimeSpan _time;
    private string _tb;
    private readonly DateTimeOffset _endTime;

    public Countdown(string tb, DateTimeOffset endTime)
    {
        _tb = tb;
        _endTime = endTime;
        DispatcherTimerSetup();
    }

    private void DispatcherTimerSetup()
    {
        DispatcherTimer = new DispatcherTimer();
        StartTime = DateTimeOffset.Now;
        _time = new TimeSpan();
        _time = _endTime - StartTime;

        DispatcherTimer.Interval = new TimeSpan(0, 0, 0, 1);
        DispatcherTimer.Tick += dispatcherTimer_Tick;

        DispatcherTimer.Start();

    }

    private void dispatcherTimer_Tick(object sender, object e)
    {
        _time = _time.Subtract(new TimeSpan(0, 0, 1));
        if (_time <= TimeSpan.Zero)
        {
            _tb = "- " + _time.ToString(@"dd\:hh\:mm\:ss");

        }
        else
        {
            _tb = "  " + _time.ToString(@"dd\:hh\:mm\:ss");
        }
    }
}

这是我的倒计时元素,我在其中创建了一个新的倒计时对象:

 public class ListCountdownElement
{
    public string CountdownElement;

    public ListCountdownElement(Incident incident, int type)
    {
        CountdownElement = "Countdown";

        switch (type)
        {
            case 1:
                if (incident.Resolvebykpiid != null)
                {
                    var endTime = incident.Resolvebykpiid.Failuretime;
                    if (endTime != null)
                    {
                        var c = new Countdown(CountdownElement, (DateTimeOffset)endTime);
                    }
                }
                break;
            case 2:
                if (incident.Firstresponsebykpiid != null)
                {
                    if (incident.Firstresponsesent != null && incident.Firstresponsesent.Value)
                    {
                        CountdownElement.Foreground = new SolidColorBrush(Colors.Green);
                        CountdownElement.Text = "sent";
                    }
                    else
                    {
                        var endTime = incident.Firstresponsebykpiid.Failuretime;
                        if (endTime != null)
                        {
                            var c = new Countdown(CountdownElement, (DateTimeOffset)endTime);
                        }
                    }

                }
                break;
            case 3:
                if (incident.Resolvebykpiid != null)
                {
                    var endTime = incident.Resolvebykpiid.Failuretime;
                    if (endTime != null)
                    {
                        var c = new Countdown(CountdownElement, (DateTimeOffset)endTime);
                    }
                }
                break;
            default:
                break;
        }
    }
}

这是 IncidentViewModel,我在其中创建倒计时元素 - 这是我绑定到的数据。

 public class IncidentViewModel
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Customer { get; set; }
    public ListStatusBar StatusBar { get; set; }
    public ListCountdownElement Countdown { get; set; }
    public ListStatusLight Status { get; set; }

    public IncidentViewModel(Incident incident, IncidentTypes type)
    {
        this.Id = incident.Ticketnumber;
        this.Title = incident.Title;
        this.Customer = incident.Customerid_account.Name;
        this.StatusBar = new ListStatusBar(incident, type);
        this.Countdown = new ListCountdownElement(incident, type.GetHashCode());
        this.Status = new ListStatusLight(incident, type);
    }
}

这是 ListViewModel,我在其中获取数据并创建 incidentViewModel 并将它们放入可观察的集合中:

public class IncidentListViewModel
{

    public ObservableCollection<Incident> Incidents;
    public ObservableCollection<IncidentViewModel> IncidentsCollection;

    public IncidentListViewModel()
    {
        IncidentsCollection = new ObservableCollection<IncidentViewModel>();
    }
    public async Task<ObservableCollection<IncidentViewModel>> GetData(string accessToken, IncidentTypes type)
    {
        Incidents = await DataLoader.LoadIncidentsData(accessToken, type.GetHashCode());
        IncidentsCollection.Clear();
        foreach (var incident in Incidents)
        {
            var incidentVm = new IncidentViewModel(incident, type);

            IncidentsCollection.Add(incidentVm);
        }

        return IncidentsCollection;
    }
}

这是列表控件的 xaml.cs,我在其中绑定到可观察集合:

public sealed partial class IncidentListControl : UserControl
{
    private readonly IncidentListViewModel _incidentListVm;
    private readonly string _accessToken;
    private readonly IncidentTypes _type;
    public IncidentListControl(string accessToken, IncidentTypes type)
    {
        this.InitializeComponent();
        this._accessToken = accessToken;
        this._type = type;
        this.ListHeader.Text = type.ToString();
        _incidentListVm = new IncidentListViewModel();
        GetIncidentData();

        //Get Incident Data and update UI
        var period = TimeSpan.FromSeconds(60);
        var periodicTimer = ThreadPoolTimer.CreatePeriodicTimer(async (source) =>
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.High,
                GetIncidentData);
        }, period);
    }

    private async void GetIncidentData()
    {
        await _incidentListVm.GetData(_accessToken, _type);
        this.ListViewIncidents.ItemsSource = _incidentListVm.IncidentsCollection;
    }
}

最后 xaml,我在其中对 TextBlocks 进行绑定:

<ListView Name="ListViewIncidents" Grid.Row="1">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid Margin="5">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="150"/>
                        <ColumnDefinition Width="200"/>
                        <ColumnDefinition Width="200"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Id}" Margin="3" Grid.Column="0" FontWeight="Bold" FontSize="12" TextWrapping="Wrap" />
                    <TextBlock Text="{Binding Title}" Margin="3" Grid.Column="1" FontWeight="Bold" FontSize="12" TextWrapping="Wrap" />
                    <TextBlock Text="{Binding Customer}" Margin="3" Grid.Column="2" FontWeight="Bold" FontSize="12" TextWrapping="Wrap" />
                    <Border Margin="3" Grid.Column="3" Height="10" BorderThickness="1" BorderBrush="Black">
                        <Rectangle  Fill="{Binding StatusBar.Color}" Width="{Binding StatusBar.Width}" HorizontalAlignment="Left" Height="10"></Rectangle>
                    </Border>
                   <TextBlock Text="{Binding Countdown.CountdownElement}" Margin="3" Grid.Column="4" FontWeight="Bold" FontSize="12" TextWrapping="Wrap" />
                    <Ellipse Margin="3" Height="10" Width="10" Stroke="Black" Fill="{Binding Status.StatusColor}" Grid.Column="5"/>
                </Grid>

            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

这只是相关部分。 希望这次更新对您有所帮助!

更新代码: Countdown.cs

public class Countdown : INotifyPropertyChanged
{
    public DispatcherTimer DispatcherTimer;
    public DateTimeOffset StartTime;
    private TimeSpan _time;
    private string _tb;
    public string Tb
    {
        get { return _tb; }
        set
        {
            if (Equals(value, _tb))
                return;

            _tb = value;
            OnPropertyChanged();
        }
    }
    private readonly DateTimeOffset _endTime;

    public Countdown( DateTimeOffset endTime)
    {
      //  _tb = tb;
        _endTime = endTime;
        DispatcherTimerSetup();
    }

    private void DispatcherTimerSetup()
    {
        DispatcherTimer = new DispatcherTimer();
        StartTime = DateTimeOffset.Now;
        _time = new TimeSpan();
        _time = _endTime - StartTime;

        DispatcherTimer.Interval = new TimeSpan(0, 0, 0, 1);
        DispatcherTimer.Tick += dispatcherTimer_Tick;

        DispatcherTimer.Start();

    }

    private void dispatcherTimer_Tick(object sender, object e)
    {
        _time = _time.Subtract(new TimeSpan(0, 0, 1));
        if (_time <= TimeSpan.Zero)
        {
            Tb = "- " + _time.ToString(@"dd\:hh\:mm\:ss");
        }
        else
        {
            Tb = "  " + _time.ToString(@"dd\:hh\:mm\:ss");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

ListCountdownElement.cs

 public class ListCountdownElement 
{
    private Countdown _countDown;
    public string CountdownElement
    {
        get {return _countDown.Tb ; }          
    }


    public ListCountdownElement(Incident incident, int type)
    {

        switch (type)
        {
            case 1:
                if (incident.Resolvebykpiid != null)
                {
                    var endTime = incident.Resolvebykpiid.Failuretime;
                    if (endTime != null)
                    {
                         _countDown = new Countdown( (DateTimeOffset)endTime);
                    }
                }
                break;
            case 2:
                if (incident.Firstresponsebykpiid != null)
                {
                    if (incident.Firstresponsesent != null && incident.Firstresponsesent.Value)
                    {
                        //CountdownElement = "sent"; --> think about that later
                    }
                    else
                    {
                        var endTime = incident.Firstresponsebykpiid.Failuretime;
                        if (endTime != null)
                        {
                            _countDown = new Countdown( (DateTimeOffset)endTime);
                        }
                    }

                }
                break;
            case 3:
                if (incident.Resolvebykpiid != null)
                {
                    var endTime = incident.Resolvebykpiid.Failuretime;
                    if (endTime != null)
                    {
                         _countDown = new Countdown((DateTimeOffset)endTime);
                    }
                }
                break;
            default:
                break;
        }
    }
}

IncidentViewModel.cs

public class IncidentViewModel
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Customer { get; set; }
    public ListStatusBar StatusBar { get; set; }
    public ListCountdownElement Countdown { get; set; }
    public ListStatusLight Status { get; set; }

    public IncidentViewModel(Incident incident, IncidentTypes type)
    {
        this.Id = incident.Ticketnumber;
        this.Title = incident.Title;
        this.Customer = incident.Customerid_account.Name;
        this.StatusBar = new ListStatusBar(incident, type);
        this.Countdown = new ListCountdownElement(incident, type.GetHashCode());
        this.Status = new ListStatusLight(incident, type);
    }
}

IncidentListControl.xaml.cs、IncidentListControl.xaml.cs 和 IncidentListViewModel.cs 未受影响

编辑:最终版本 新 ListCountdownElement.cs:

public class ListCountdownElement : INotifyPropertyChanged
{
    public DispatcherTimer DispatcherTimer;
    public DateTimeOffset StartTime;
    private TimeSpan _time;
    private DateTimeOffset _endTime;
    private string _countdownElement;
    public string CountdownElement
    {
        get { return _countdownElement; }
        set
        {
            if (Equals(value, _countdownElement))
                return;

            _countdownElement = value;
            OnPropertyChanged();
        }
    }

    public ListCountdownElement(Incident incident, int type)
    {
        switch (type)
        {
            case 1:
                if (incident.Resolvebykpiid != null)
                {
                    _endTime = (DateTimeOffset)incident.Resolvebykpiid.Failuretime;
                    DispatcherTimerSetup();
                }
                break;
            case 2:
                if (incident.Firstresponsebykpiid != null)
                {
                    if (incident.Firstresponsesent != null && incident.Firstresponsesent.Value)
                    {
                        //CountdownElement = "sent"; --> think about that later
                    }
                    else
                    {
                        _endTime = (DateTimeOffset)incident.Firstresponsebykpiid.Failuretime;
                        DispatcherTimerSetup();
                    }

                }
                break;
            case 3:
                if (incident.Resolvebykpiid != null)
                {
                    _endTime = (DateTimeOffset)incident.Resolvebykpiid.Failuretime;
                    DispatcherTimerSetup();
                }
                break;
            default:
                break;
        }

    }
    private void DispatcherTimerSetup()
    {
        DispatcherTimer = new DispatcherTimer();
        StartTime = DateTimeOffset.Now;
        _time = new TimeSpan();
        _time = _endTime - StartTime;

        DispatcherTimer.Interval = new TimeSpan(0, 0, 0, 1);
        DispatcherTimer.Tick += dispatcherTimer_Tick;

        DispatcherTimer.Start();

    }

    private void dispatcherTimer_Tick(object sender, object e)
    {
        _time = _time.Subtract(new TimeSpan(0, 0, 1));
        if (_time <= TimeSpan.Zero)
        {
            CountdownElement = "- " + _time.ToString(@"dd\:hh\:mm\:ss");
        }
        else
        {
            CountdownElement = "  " + _time.ToString(@"dd\:hh\:mm\:ss");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

看来您没有触发正确的事件来让 ui 知道文本已更改。 您显示的代码对于您要完成的任务来说看起来也有点复杂...

首先确保 CountDown class 继承 INotifyPropertyChanged。添加该接口时,您还需要向该接口添加以下代码 class:

public event PropertyChangedEventHandler PropertyChanged;

[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    var handler = PropertyChanged;
    if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}

之后,将 _tb 字段更改为实际的 属性,如下所示:

private string _tb;
public string Tb
{
    get { return _tb; }
    set
    {
        if(Equals(value, _tb))
            return;

        _tb = value;
        OnPropertyChanged();
    }
}

**** 重要 ****

在dispatcherTimer_Tick事件中,改变Tb的值(属性),而不是_tb字段。

现在您需要将 属性 路由到 UI,因此在 ListCountdownElement class 中,也将 CountdownElement 字段更改为真实的 属性。

public string CountdownElement
{ get { return _countDown.Tb; } }

唯一要做的就是添加一个 CountDown 类型的 _countDown 字段,并确保在您的 ListCountdownElement 构造函数中分配它。

我想这应该可以解决它...(虽然没有测试 :))