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 クラスにラベルを ref として渡して更新しようとしましたが、うまくいきません (別のスレッドから UI 要素にアクセスできません)。

値が変更されるたびに MyTimer.Cycle が更新されるように、ラベルに MyTimer.Cycle を表示する良い方法は何ですか?

各 MyTimer をコードとは異なるラベルに「バインド」できる必要があります (または、ラベルにまったくバインドできません)。

4

4 に答える 4

0

問題の最も簡単な解決策は、DispatchTimersを使用することです。ディスパッチタイマーは、スレッドの代わりにWindowsメッセージキューを使用して、タイマーティックイベントをディスパッチします。これにより、クロススレッドの問題が発生しなくなります。別のスレッドで作業しなくなったため、計算コストの高い処理を行うとUIがロックされる可能性があることに注意してください。また、メッセージキューでのディスパッチの性質により、タイミングはそれほど正確ではありません。

于 2013-01-15T13:18:07.840 に答える
0

WPF では、ビュー (XAML) に関連付けられたビューモデル (C#) があります。
MVVM に慣れていない場合は、これを読んでください。

次に、ViewModel は、View がバインドするプロパティ (Cycle と呼びましょう) を公開します。

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

次に、ViewModel の値を別のスレッドから更新する必要がある場合は、次のようにします。

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

これにより、UI スレッドで更新ロジックが実行されます。

于 2013-01-15T13:23:40.737 に答える
0

BeginInvokeラベルのプロパティのorInvokeメソッドを使用して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 のデータ コンテキストが a に設定されます。このプロパティは、親 UI 要素から子 UI 要素に自動的に基づいているnew MyTimer();ため(子が独自の を定義していない限り)、Window/UserControl のすべての要素が a を持つようになります。このオブジェクトの。DataContextDataContextDataContext

次に、このオブジェクトのプロパティへのバインディングを作成できます。デフォルトでは、すべてのバインディングは、DataContextそれが配置されているコントロールの に対して相対的です。

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

したがって、前の例では、バインディングはラベルの content プロパティにありました。したがって、この場合、Content は DataContext (MyTimer) の「Cycle」プロパティの値に自動的に設定されます。

ただし、1 つのキャッチがあります。このサンプルをそのまま実行すると、WPF はフォームの読み込み時に値を取得しますが、ラベルは二度と更新されません! ここで UI を更新するための鍵は、INotifyPropertyChangedインターフェイスを実装することです。

このインターフェイスは、プロパティ (Cycles など) が変更されるたびにリスナーに通知するだけです。すばらしいことに、Binding はこのインターフェイスを自動的にサポートし、ソースが を実装すると自動的に変更を伝達します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.
}

そして出来上がり!タイマーは、cycles プロパティを使用して 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 に答える