Обновление пользовательского интерфейса приложения Магазина Windows - PullRequest
7 голосов
/ 29 марта 2012

Я пишу игрушечное приложение для Магазина Windows для Windows 8. У него всего одна страница xaml с TextBlock.Страница имеет класс MyTimer как DataContext:

this.DataContext = new MyTimer();

MyTimer реализует INotifyPropertyChanged, а обновление свойства Time выполняется с помощью таймера:

public MyTimer(){
    TimerElapsedHandler f = new TimerElapsedHandler(NotifyTimeChanged);
    TimeSpan period = new TimeSpan(0, 0, 1);
    ThreadPoolTimer.CreatePeriodicTimer(f, period);
}

с

private void NotifyTimeChanged(){
    if (this.PropertyChanged != null){
        this.PropertyChanged(this, new PropertyChangedEventArgs("Time"));
    }
}

TextBlock имеет привязку данных по времени

<TextBlock Text="{Binding Time}" />

Когда я запускаю приложение, у меня возникает следующее исключение:

System.Runtime.InteropServices.COMException was unhandled by user code

Ссообщение

The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

Реальная проблема в том, что я обновляю свойство класса MyTimer, а не сам графический интерфейс, я не могу понять это, но я думаю, что решениеследует использовать что-то вроде этот .

Ответы [ 3 ]

7 голосов
/ 30 марта 2012

Да, вы уведомляете об изменениях свойств из потока пула потоков, а не из потока пользовательского интерфейса. Вам необходимо направить уведомление обратно в поток пользовательского интерфейса при обратном вызове таймера. Теперь ваша модель представления отделена от вашего представления (что хорошо), поэтому она не имеет прямой связи с инфраструктурой Dispatcher. Итак, что вы хотите сделать, это передать ему SynchronizationContext, с которым можно общаться. Для этого вам нужно захватить текущий SynchronizationContext во время построения или разрешить его явную передачу конструктору, который подходит для тестов или если вы инициализируете объект из потока пользовательского интерфейса для начала.

Весь Шебанг будет выглядеть примерно так:

public class MyTimer
{
    private SynchronizationContext synchronizationContext;

    public MyTimer() : this(SynchronizationContext.Current)
    {
    }

    public MyTimer(SynchronizationContext synchronizationContext)
    {
        if(this.synchronizationContext == null)
        {
            throw new ArgumentNullException("No synchronization context was specified and no default synchronization context was found.")
        }

        TimerElapsedHandler f = new TimerElapsedHandler(NotifyTimeChanged);
        TimeSpan period = new TimeSpan(0, 0, 1);
        ThreadPoolTimer.CreatePeriodicTimer(f, period);
    }

    private void NotifyTimeChanged()
    {
        if(this.PropertyChanged != null)
        {
            this.synchronizationContext.Post(() =>
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Time"));
                });
        }
    }
}
5 голосов
/ 30 марта 2012

Одним из способов сделать это является ожидание Task.Delay() в цикле вместо использования таймера:

class MyTimer : INotifyPropertyChanged
{
    public MyTimer()
    {
        Start();
    }

    private async void Start()
    {
        while (true)
        {
            await Task.Delay(TimeSpan.FromSeconds(1));
            PropertyChanged(this, new PropertyChangedEventArgs("Time"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public DateTime Time { get { return DateTime.Now; } }
}

Если вы вызовете конструктор в потоке пользовательского интерфейса, он также вызовет PropertyChanged там,И хорошо, что точно такой же код будет работать, например, и в WPF (в .Net 4.5 и C # 5).

1 голос
/ 17 сентября 2012

как насчет кода из этого блога:

http://metrowindows8.blogspot.in/2011/10/metro-tiles.html

Это сработало для меня.Мне пришлось передать объект ThreadPoolTimer моей функции делегата

...