1

我有以下课程:

class MyTimer
{
    class MyTimerInvalidType : SystemException
    {
    }
    class MyTimerNegativeCycles : SystemException
    {
    }

    private Timer timer = new Timer(1000);
    private int cycles = 0;

    public int Cycle
    {
        get
        {
            return this.cycles;
        }

        set
        {
            if(value >= 0)
                this.cycles = value;
            else
                throw new MyTimerNegativeCycles();
        }
    }

    private void timer_Tick(object sender, ElapsedEventArgs e)
    {
        try
        {
            this.Cycle--;
        }
        catch
        {
            this.Cycle = 0;
            timer.Stop();
        }
    }

    public MyTimer()
    {
        this.Cycle = 20;

        timer.Elapsed += new ElapsedEventHandler(timer_Tick);
        timer.Start();
    }
}

在我的 MainWindow 类中,我有一个列表,我在按下按钮时添加了一个 MyTimer:

private List<MyTimer> timers = new List<MyTimer>();

private void testbtn_Click(object sender, RoutedEventArgs e)
{
    timers.Add(new MyTimer());
}

我试图将标签作为引用传递给 MyTimer 类并对其进行更新,但这不起作用(无法从另一个线程访问 UI 元素)。

在标签中显示 MyTimer.Cycle 以便在每次更改值时更新的好方法是什么?

我必须能够将每个 MyTimer 与代码“绑定”到不同的标签(或根本不将其绑定到标签)。

4

4 回答 4

0

解决问题的最简单方法是使用DispatchTimers。调度计时器使用 Windows 消息队列而不是线程来调度计时器滴答事件。这将使您没有跨线程问题。请记住,您不再在其他线程上工作,并且如果您执行任何计算成本很高的操作,可能会锁定 UI。此外,由于消息队列上调度的性质,时间不太准确。

于 2013-01-15T13:18:07.840 回答
0

在 WPF 中,您将拥有一个与您的视图 (XAML) 关联的 ViewModel (C#)。
如果您不熟悉 MVVM,请阅读此内容。

然后 ViewModel 将公开一个属性(我们称之为 Cycle),View 将在该属性上绑定:

<Label Content="{Binding Cycle}" />

然后,如果必须从另一个线程更新 ViewModel 中的值,请执行以下操作:

Application.Current.Dispatcher.Invoke(new Action(() => 
{
    //Update here
}));

这将在 UI 线程上执行更新逻辑。

于 2013-01-15T13:23:40.737 回答
0

您应该使用标签属性的BeginInvokeorInvoke方法Dispatcher来更改标签上的任何内容或调用它的任何方法:

private void timer_Tick(object sender, ElapsedEventArgs e)
{
    try
    {
        this.Cycle--;
        this.label.Dispatcher.BeginInvoke(new Action(
            () => { label.Text = this.Cycle.ToString(); } ));
    }
    catch
    {
        this.Cycle = 0;
        timer.Stop();
    }
}

Dispatcher请参阅类Dispatcher属性的备注部分。

于 2013-01-15T13:49:23.697 回答
0

如果您是 WPF 的新手,我强烈建议您阅读一些有关DataBindingData Templating的内容。

首先,在旧的 UI 模型(如 Windows 窗体)中显示 Windows 数据的最简单方法一直是在代码隐藏中设置一些 UI 属性。这在 WPF 中发生了巨大的变化,现在的目标是让 UI 查看业务对象(如您的 MyTimer)并相应地设置 UI。

首先,我们需要将您的业务对象公开给您的应用程序的 xaml。

Me.DataContext = new MyTimer();

这会将 Window/UserControl 的数据上下文设置为 anew MyTimer();因为该DataContext属性自动基于从父 UI 元素到子 UI elelement(除非子定义它自己的DataContext),所以 Window/UserControl 中的每个元素现在都将具有DataContext这个对象的。

接下来我们可以创建一个绑定到这个对象的属性。默认情况下,所有绑定都相对于DataContext它所在的控件。

<Label Content="{Binding Cycle}" />

所以在前面的例子中,绑定是在标签的 content 属性上。所以在这种情况下,它会自动将 Content 设置为 DataContext (MyTimer) 中的“Cycle”属性的值!

然而,有一个问题。如果您按原样运行此示例,WPF 将在表单加载时获取该值,但它不会再次更新标签!这里更新 UI 的关键是实现INotifyPropertyChanged接口。

每当属性(例如您的 Cycles)发生更改时,此接口都会简单地告诉任何侦听器。很棒的是 Bindings 自动支持这个接口,并且会在你的源实现时自动传播更改INotifyPropertyChanged

public class MyTimer : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int cycles;

    public int Cycles
    {
        get
        {
            return cycles;
        }
        set
        {
            if (cycles < 0)
            {
                throw new ArgumentOutOfRangeException("value", "Cycles cannot be set to a number smaller than 0.");
            }
            else if(value <> cycles)
            {
                cycles = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(Me, new PropertyChangedEventArgs("Cycles"))
                }
            }
        }
    }

    //insert your constructor(s) and timer code here.
}

瞧!您的计时器现在将使用它的循环属性更新 UI。

但是,您还注意到您将 MyTimer 对象存储在列表中。如果您要将它们放在一个ObservableCollection(默认实现INotifyCollectionChanged- INotifyPropertyChanged 的​​集合变体)中,您可以做其他巧妙的技巧:

在您的 Window/UserControl 构造函数中:

ObservableCollection<MyTimer> timers = New ObservableCollection<MyTimer>();
timers.Add(New MyTimer());
DataContext = timers;

然后你可以在你的 xaml 中一次显示它们:

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Label>
                <TextBlock Text="{Binding StringFormat='Cycles Remaining: {0}'}" />
            </Label>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
于 2013-01-15T14:41:38.243 回答