Порядок инициализации управления Fiasco

Рассмотрим следующий код:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Slider ValueChanged="slider_ValueChanged/>
        <TextBox x:Name="counter"/>
    </StackPanel>
</Window>

а также

namespace Project1
{
    public partial class Window1 : Window
    {
        public MainWindow() { InitializeComponent(); }

        void slider_ValueChanged(object sender,
            RoutedPropertyChangedEventArgs<double> e)
        {
            counter.Text = e.NewValue.ToString();
        }
    }
}

Ползунок поднимет ее ValueChanged событие во время инициализации пока counter все еще null,

Это пример более крупной проблемы, с которой я столкнулся при использовании WPF, что события пользовательского интерфейса могут запускаться в любое время, и что нет единого места, где я мог бы поместить свой код инициализации, чтобы он гарантированно выполнялся после всех указатели, принадлежащие системе WPF, были инициализированы, но до того, как сработали какие-либо события пользовательского интерфейса.

Какой самый элегантный способ справиться с этим? Тот факт, что этот конкретный пример должен использовать привязку данных, не имеет значения.

2 ответа

Решение

Истинный ответ на этот вопрос заключается в использовании шаблона MVVM, в котором код окна за файлами практически не содержит кода инициализации.

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

Естественно, вы полностью контролируете, как инициализируются ваши view-модели.

Есть много способов справиться с этим, в зависимости от вашей ситуации

Во-первых, вы можете просто распознать тот факт, что объект не может быть инициализирован, и проверить это перед обработкой. Например,

if (counter.Text != null)
    counter.Text = e.NewValue.ToString();

Во-вторых, вы можете прикрепить свои события к событию Loaded объекта, чтобы они не срабатывали до тех пор, пока объект не будет инициализирован.

void Counter_Loaded(object sender, EventArgs e)
{
    slider.ValueChanged += Slider_ValueChanged;
}

void Counter_Unloaded(object sender, EventArgs e)
{
    slider.ValueChanged -= Slider_ValueChanged;
}

И наконец, вы можете использовать диспетчер WPF для запуска событий в потоке пользовательского интерфейса в другом DispatcherPriority. По умолчанию Normal, который бежит после Loaded, Render, а также DataBind операции

Dispatcher.BeginInvoke(DispatcherPriority.DataBind, 
    new Action(delegate() { counter.Text = e.NewValue.ToString(); }));
Другие вопросы по тегам