Подтвердить что ты не робот

С компилируемыми привязками (x: bind), почему мне нужно вызывать Bindings.Update()?

В настоящее время я экспериментирую с новыми скомпилированными привязками и достиг (снова) точки, где мне не хватает кусочка в головоломке: зачем мне звонить Bindings.Update? До сих пор я думал, что реализовать INotifyPropertyChanged достаточно?

В моем примере графический интерфейс отображает только правильные значения, если я вызываю этот таинственный метод (который автогенерируется скомпилированными привязками).

Я использую пользовательский элемент управления со следующим (здесь упрощенным) синтаксисом xaml:

<UserControl>
  <TextBlock Text="x:Bind TextValue"/>
</UserControl>

где TextValue - это простое свойство зависимостей этого пользовательского элемента управления. На странице я использую этот элемент управления как:

<Page>
  <SampleControl TextValue="{x:Bind ViewModel.Instance.Name}"/>
</Page>

где:

  • ViewModel - стандартное свойство, установленное до запуска InitializeComponent()
  • Instance - это простой объект, реализующий INotifyPropertyChanged

После загрузки Instance я создаю событие с измененным свойством для Instance. Я могу даже отлаживать строку, где свойство depency TextValue пользовательского элемента управления получает правильное значение, но ничего не отображается. Только если я вызываю Bindings.Update(), отображается значение. Что мне здесь не хватает?

Обновление

Я не работаю с {x:Bind ... Mode=OneWay}.

Больше кода

Person.cs:

using System.ComponentModel;
using System.Threading.Tasks;

namespace App1 {
    public class Person : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;

        private string name;
        public string Name { get {
                return this.name;
            }
            set {
                name = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Name"));
            }
        }
    }

    public class ViewModel : INotifyPropertyChanged {

        public event PropertyChangedEventHandler PropertyChanged;

        private Person instance;
        public Person Instance {
            get {
                return instance;
            }
            set {
                instance = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Instance"));
            }
        }

        public Task Load() {
            return Task.Delay(1000).ContinueWith((t) => {
                var person = new Person() { Name = "Sample Person" };                
                this.Instance = person;
            });
        }


    }
}

SampleControl.cs:

<UserControl
    x:Class="App1.SampleControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="100"
    d:DesignWidth="100">

    <TextBlock Text="{x:Bind TextValue, Mode=OneWay}"/>

</UserControl>

SampleControl.xaml.cs:

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App1 {
    public sealed partial class SampleControl : UserControl {

        public SampleControl() {
            this.InitializeComponent();
        }

        public string TextValue {
            get { return (string)GetValue(TextValueProperty); }
            set { SetValue(TextValueProperty, value); }
        }

        public static readonly DependencyProperty TextValueProperty =
            DependencyProperty.Register("TextValue", typeof(string), typeof(SampleControl), new PropertyMetadata(string.Empty));

    }
}

MainPage.xaml:

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <local:SampleControl TextValue="{x:Bind ViewModel.Instance.Name, Mode=OneWay}"/>
    </StackPanel>
</Page>

MainPage.xaml.cs:

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App1 {

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.DataContext = new ViewModel();
            this.Loaded += MainPage_Loaded;
            this.InitializeComponent();
        }

        public ViewModel ViewModel {
            get {
                return DataContext as ViewModel;
            }
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e) {
            ViewModel.Load();
            Bindings.Update(); /* <<<<< Why ????? */
        }
    }
}

Еще одно обновление

Я обновил метод Load для использования задачи (см. код выше)!

4b9b3361

Ответ 1

Иногда данные, которые вы хотите показать, недоступны (например, возвращены с сервера или базы данных) до нескольких секунд после загрузки и рендеринга вашей страницы. Это особенно верно, если вы вызываете свои данные в фоновом/асинхронном процессе, который освобождает ваш пользовательский интерфейс для визуализации без зависания.

Догадаться до сих пор?

Теперь создайте привязку; скажем что-то вроде этого:

<TextBlock Text="{x:Bind ViewModel.User.FirstName}" />

Значение вашего свойства ViewModel в вашем коде-коде будет иметь реальное значение и будет очень просто связываться. С другой стороны, у вашего пользователя не будет значения, поскольку он еще не возвращается с сервера. В результате не может отображаться ни это, ни свойство FirstName для пользователя, правильно?

Затем ваши данные обновляются.

Вы считаете, что привязка автоматически обновится, когда вы установите значение объекта User на реальный объект. Особенно, если вы потратили время на создание свойства INotifyPropertyChanged, верно? Это было бы верно с традиционным {Binding}, потому что режим привязки по умолчанию - OneWay.

Что такое режим привязки OneWay?

Режим привязки OneWay означает, что вы можете обновить свои свойства модели бэкэнд, которые реализуют INotifyPropertyChanged, и элемент интерфейса, привязанный к этому свойству, будет отражать изменение данных/значений. Это замечательно.

Почему это не работает?

Это НЕ, потому что {x: Bind} не поддерживает Mode = OneWay, потому что по умолчанию он имеет значение Mode = OneTime. Чтобы вернуться к традиционному {Binding} по умолчанию, выберите Mode = OneWay и скомпилировали по умолчанию {x: Bind} значение Mode = OneTime.

Что такое режим привязки OneTime?

Режим привязки OneTime означает, что вы привязываетесь к базовой модели только один раз, во время загрузки/отображения элемента интерфейса с привязкой. Это означает, что если ваши базовые данные еще недоступны, они не могут отображать эти данные, и как только данные будут доступны, они не будут отображать эти данные. Зачем? Поскольку OneTime не контролирует INotifyPropertyChanged. Он читается только при загрузке.

Режимы (от MSDN): для привязок OneWay и TwoWay динамические изменения источника не распространяются автоматически на цели, не предоставляя некоторую поддержку от источника. Вы должны реализовать интерфейс INotifyPropertyChanged для исходного объекта, чтобы источник мог сообщать об изменениях через события, которые прослушивает механизм привязки. Для С# или Microsoft Visual Basic используйте System.ComponentModel.INotifyPropertyChanged. Для расширений компонентов Visual С++ (С++/CX) реализуйте Windows:: UI:: Xaml:: Data:: INotifyPropertyChanged.

Как решить эту проблему?

Есть несколько способов. Первым и самым простым является изменение привязки с ="{x:Bind ViewModel.User.FirstName} до ="{x:Bind ViewModel.User.FirstName, Mode=OneWay}. Это будет отслеживать события INotifyPropertyChanged.

Настало время предупредить вас, что использование OneTime по умолчанию является одним из многих способов: {x: Bind} пытается повысить производительность привязки. Это потому, что OneTime является самым быстрым с наименьшими требованиями к памяти. Изменение привязки к OneWay подрывает это, но может потребоваться для вашего приложения.

Другой способ устранить эту проблему и по-прежнему поддерживать преимущества производительности, которые возникают из коробки с помощью {x: Bind}, - это вызвать Bindings.Update(); после того, как ваша модель представления полностью подготовила ваши данные для представления. Это легко, если ваша работа асинхронна, но, как и ваш пример выше, если вы не можете быть уверены, что таймер может быть вашим единственным жизнеспособным вариантом.

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

Надеюсь, это объясняет, что происходит.

Удачи!

Ответ 2

Наконец, я обнаружил ошибку: я использовал операцию, основанную на задачах, для загрузки моей модели представления, которая привела к тому, что свойство зависимостей было задано с помощью неправильного потока (я думаю). Он работает, если я устанавливаю свойство Instance через диспетчер.

    public Task Load() {
        return Task.Delay(1000).ContinueWith((t) => {
            var person = new Person() { Name = "Sample Person" };
            Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
            () => {
                this.Instance = person;
            });                
        });
    }

Но не было никакого исключения, просто gui не показывал значения!

Ответ 3

В то время как "традиционные" привязки по умолчанию равны "одностороннему" (или, в некоторых случаях, двум путям), скомпилированные привязки по умолчанию "одноразовые". Просто измените режим при настройке привязки:

<TextBlock Text="{x:Bind TextValue, Mode=OneWay}" />

Ответ 4

Прежде всего, режим привязки по умолчанию x:Bind равен OneTime, вам нужно изменить его на OneWay как ответ, упомянутый выше, чтобы заставить его работать, если вы вызываете метод RaisePropertyChanged.

Кажется, что что-то не так с вашим кодом привязки данных. ПОЖАЛУЙСТА, вставьте весь код, чтобы увидеть источник этой проблемы.