2013-01-15 65 views
1

我有以下类:C#WPF类属性标记

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(); 
    } 
} 

在我的主窗口类我有一个列表我一个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“绑定”到代码中的不同标签(或者根本不将它绑定到标签)。

+0

你是什么意思与“传递一个标签到'MyTimer'类”? –

+0

@ nico-schertler我向MyTimer提供了一个方法,它将一个标签ref作为参数,然后试图通过它将一个ref传递给一个标签。 – Kyto

回答

0

你应该用你的标签的Dispatcher财产BeginInvokeInvoke方法来改变什么在标签上或调用任何它的方法:

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 classDispatcher property的注释部分。

0

最简单的解决方案是使用DispatchTimers。调度计时器使用Windows消息队列而不是线程来调度计时器滴答事件。这将使它不会出现跨线程问题。请记住,您不再使用其他线程,并且可能会锁定用户界面,如果您执行的任何操作都很昂贵。同样由于在消息队列上调度的性质,时序不太准确。

0

在WPF中,您将拥有与View(XAML)关联的ViewModel(C#)。
如果您不熟悉MVVM,请阅读本文。

然后视图模型会暴露属性(我们称之为周期),该视图将绑定:

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

那么,如果在视图模型的价值必须从另一个线程更新,像这样做:

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

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

0

如果您是WPF的新手,我强烈建议您阅读一下DataBindingData Templating

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

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

Me.DataContext = new MyTimer(); 

这设定了窗口/用户控件是一个new 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的收集变体)中,你可以做其他巧妙的技巧:

在窗口/用户控件的构造函数:

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>