c# windows-runtime winrt-xaml win-universal-app

c# - Con enlaces compilados(x: bind), ¿por qué tengo que llamar a Bindings.Update()?



windows-runtime winrt-xaml (4)

Mientras que los enlaces "tradicionales" son predeterminados a "unidireccional" (o bidireccional en algunos casos), los enlaces compilados se ajustan por defecto a "una sola vez". Simplemente cambie el modo cuando configure el enlace:

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

Actualmente estoy experimentando con las nuevas vinculaciones compiladas y he alcanzado (una vez más) un punto en el que me falta un punto en el rompecabezas: ¿por qué tengo que llamar a Bindings.Update ? Hasta ahora, ¿pensé que implementar INotifyPropertyChanged es suficiente?

En mi ejemplo, la GUI solo muestra los valores correctos, si llamo a este misterioso método (que es autogenerado por los enlaces compilados).

Estoy usando un control de usuario con la siguiente sintaxis xaml (aquí simplificada):

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

donde TextValue es una propiedad de dependencia simple de este control de usuario. En una página, estoy usando este control como:

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

dónde:

  • ViewModel es una ViewModel estándar que se establece antes de ejecutar InitializeComponent()
  • Instance es un objeto simple que implementa INotifyPropertyChanged

Después de cargar Instance , elevo un evento de propiedad cambiado para Instance . Incluso puedo depurar en la línea, donde la propiedad de TextValue del control de usuario obtiene el valor correcto , pero no se muestra nada. Solo si llamo a Bindings.Update() , se muestra el valor. ¿Que me estoy perdiendo aqui?

Actualizar

Yo tampoco trabajo con {x:Bind ... Mode=OneWay} tampoco.

Más código

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 ????? */ } } }

Una actualización más

Actualicé el método Load para usar la tarea (ver el código de arriba)!


En primer lugar, el modo de enlace predeterminado de x:Bind es OneTime , debe cambiarlo a OneWay como la respuesta mencionada anteriormente para que funcione si llama RaisePropertyChanged método RaisePropertyChanged .

Parece que algo ha fallado con su código de enlace de datos. POR FAVOR pegue todo el código involucrado para permitirnos ver el origen de este problema.


Finalmente, encontré el error: estaba usando una operación basada en tareas para cargar mi modelo de vista, lo que resultó en establecer la propiedad de dependencia por el hilo incorrecto (creo). Funciona si configuro la propiedad Instance través del despachador.

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

¡Pero no hubo excepción, solo la GUI no mostró ningún valor!


En ocasiones, los datos que desea mostrar no están disponibles (como los devueltos por el servidor o la base de datos) hasta varios segundos después de que su página se haya cargado y procesado. Esto es especialmente cierto si llama sus datos en un proceso de fondo / asincrónico que libera su IU para renderizar sin problemas.

¿Tiene sentido hasta ahora?

Ahora crea un enlace; digamos algo como esto:

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

El valor de su propiedad ViewModel en su código subyacente tendrá un valor real y se vinculará muy bien. Su usuario, por otro lado, no tendrá un valor porque aún no ha sido devuelto por el servidor. Como resultado, ni esa ni la propiedad FirstName del usuario se pueden mostrar, ¿verdad?

Entonces tus datos se actualizan.

Podría pensar que su enlace se actualizaría automáticamente cuando establezca el valor del objeto Usuario en un objeto real. Especialmente si se tomó el tiempo para convertirlo en una propiedad INotifyPropertyChanged, ¿verdad? Eso sería cierto con {Binding} tradicional porque el modo de enlace predeterminado es OneWay.

¿Cuál es el modo de enlace OneWay?

El modo de enlace OneWay significa que puede actualizar las propiedades de su modelo backend que implementan INotifyPropertyChanged y el elemento UI vinculado a esa propiedad reflejará el cambio de datos / valores. Es maravilloso.

Por que no funciona?

NO es porque {x: Bind} no sea compatible con Mode = OneWay, es porque de manera predeterminada es Mode = OneTime. Para recapitular, el valor predeterminado de {Binding} es Mode = OneWay y compilado {x: Bind} se establece por defecto en Mode = OneTime.

¿Cuál es el modo de enlace OneTime?

El modo de enlace OneTime significa que se vincula al modelo subyacente solo una vez, en el momento de cargar / renderizar el elemento UI con el enlace. Esto significa que si sus datos subyacentes aún no están disponibles, no puede mostrar esos datos y, una vez que los datos estén disponibles, no mostrarán esos datos. ¿Por qué? Porque OneTime no supervisa INotifyPropertyChanged. Solo lee cuando se carga.

Modos (desde MSDN ): para enlaces OneWay y TwoWay, los cambios dinámicos a la fuente no se propagan automáticamente al destino sin proporcionar algún soporte de la fuente. Debe implementar la interfaz INotifyPropertyChanged en el objeto de origen para que el origen pueda informar los cambios a través de los eventos que el motor de enlace escucha. Para C # o Microsoft Visual Basic, implemente System.ComponentModel.INotifyPropertyChanged. Para las extensiones de componentes de Visual C ++ (C ++ / CX), implemente Windows :: UI :: Xaml :: Data :: INotifyPropertyChanged.

¿Cómo resolver este problema?

Hay algunas maneras. El primero y más fácil es cambiar su enlace de ="{x:Bind ViewModel.User.FirstName} to ="{x:Bind ViewModel.User.FirstName, Mode=OneWay} . Al hacerlo, se controlarán los eventos INotifyPropertyChanged.

Este es el momento adecuado para advertirle que el uso de OneTime de manera predeterminada es una de las muchas maneras en que {x: Bind} intenta mejorar el rendimiento del enlace. Esto se debe a que OneTime es el más rápido posible con la menor cantidad de memoria requerida. Cambiar su enlace a OneWay lo socava, pero podría ser necesario para su aplicación.

La otra forma de solucionar este problema y aún mantener los beneficios de rendimiento que salen de la caja con {x: Bind} es llamar a Bindings.Update(); después de que su modelo de vista haya preparado completamente sus datos para la presentación. Esto es fácil si su trabajo es asincrónico, pero, como su ejemplo anterior, si no puede estar seguro de que un temporizador sea su única opción viable.

Eso apesta, por supuesto, porque un temporizador implica la hora del reloj, y en dispositivos lentos como un teléfono, la hora del reloj podría no aplicarse correctamente. Esto es algo que cada desarrollador tendrá que resolver específicamente para su aplicación, es decir, ¿cuándo estará su información completamente cargada y lista?

Espero que esto explique lo que está sucediendo.

¡La mejor de las suertes!