c# - plantillas - wpf styles templates
¿Cómo funcionan los motores de enlace de datos bajo el capó? (3)
Su pregunta es realmente interesante, pero su alcance es realmente muy grande.
Una herramienta realmente útil en estas situaciones es ILSpy, que le permite ver las implementaciones del marco.
Una cosa con la que tengo problemas es la siguiente declaración:
La respuesta que obtuve fue que el motor de enlace de datos en C # está estrechamente vinculado con la interfaz de usuario de WPF / Windows Forms
Estoy en desacuerdo; el motor de enlace de datos está estrechamente vinculado a la implementación de evento de .Net, pero Target y Source pueden ser cualquier cosa, la mayoría de los ejemplos serán Windows Forms, WPF o ASP.Net porque son los front ends más comunes para los lenguajes .Net, pero es Es perfectamente posible utilizar enlaces múltiples en otros escenarios sin una IU también.
¿Qué sucede cuando agregas un enlace bidireccional? Bueno, si miramos la fuente de MultiBinding notamos algunas cosas interesantes:
- Expone una propiedad BindingMode que describe el escenario de enlace, normalmente
OneWay
oOneWay
- Expone dos eventos interesantes:
NotifyOnSourceUpdated
yNotifyOnTargetUpdated
Que tienen la forma básica:
// System.Windows.Data.MultiBinding
/// <summary>Gets or sets a value that indicates whether to raise the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event when a value is transferred from the binding target to the binding source.</summary>
/// <returns>true if the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event will be raised when the binding source value is updated; otherwise, false. The default value is false.</returns>
[DefaultValue(false)]
public bool NotifyOnSourceUpdated
{
get
{
return base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);
}
set
{
bool flag = base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);
if (flag != value)
{
base.CheckSealed();
base.ChangeFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated, value);
}
}
}
es decir, usamos eventos para decirnos cuándo se actualiza la fuente ( OneWay
) y cuándo el destino también se actualiza (para el enlace de TwoWay
)
Tenga en cuenta que también hay una clase PriorityBinding
que funciona de manera similar, excepto que puede suscribirse a múltiples fuentes de datos, y priorizará la que devuelva los datos lo antes posible.
Así que la forma de cómo funciona esto es clara: cuando creamos un enlace, nos suscribimos a cambios en un lado (para actualizaciones de solo lectura) o en ambos lados (cuando los datos se pueden cambiar en la GUI, por ejemplo, y enviar de vuelta a la fuente de datos), con todas las notificaciones administradas a través de eventos.
La siguiente pregunta es, realmente, ¿quién maneja los eventos? La respuesta simple es que tanto el objetivo como la fuente lo hacen. Es por eso que la implementación de INotifyPropertyChanged
es importante, por ejemplo, todos los enlaces realmente lo hacen es crear un contrato sobre cómo ambas partes deberían suscribirse a los cambios de los demás, es el contrato al que el objetivo y la fuente están estrechamente vinculados, realmente.
ObservableCollection es un caso de prueba interesante de estudiar, ya que se usa ampliamente en aplicaciones GUI para promover actualizaciones en la fuente de datos a la IU y para enviar cambios a los datos en la IU de nuevo a la fuente de datos subyacente.
Observe (al mirar el código) cómo el evento real para comunicar que las cosas han cambiado es realmente simple, PERO el código para administrar Agrega, Quita, Actualiza realmente depende de la consistencia a través de la propiedad SimpleMonitor ( BlockReentrancy
y CheckReentrancy
) - es efectivamente garantizando que las operaciones sean atómicas y que los suscriptores sean notificados de los cambios en el orden en que ocurren Y que la colección subyacente sea consistente con los actualizados.
Esta es realmente la parte difícil de toda la operación.
En resumen, la implementación de DataBinding en .Net no está estrechamente acoplada a las tecnologías GUI; es solo que la mayoría de los ejemplos presentarán DataBinding en el contexto de las aplicaciones Windows Forms, WPF o ASP.Net. El enlace de datos real es impulsado por eventos, y para que pueda aprovecharlo, es más importante sincronizar y gestionar los cambios en sus datos: el marco de enlace de datos solo le permitirá acoplar Target y Source en actualizaciones de datos compartidas a través del contrato ( Interfaces) define.
Que te diviertas ;-)
EDITAR:
Me senté y creé dos clases, MyCharacter
y MyCharacterAttribute
con el objetivo expreso de configurar MyCharacter
MyCharacterAttribute
entre los atributos Health
y HealthValue
:
public class MyCharacter : DependencyObject
{
public static DependencyProperty HealthDependency =
DependencyProperty.Register("Health",
typeof(Double),
typeof(MyCharacter),
new PropertyMetadata(100.0, HealthDependencyChanged));
private static void HealthDependencyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
}
public double Health
{
get
{
return (double)GetValue(HealthDependency);
}
set
{
SetValue(HealthDependency, value);
}
}
public void DrinkHealthPotion(double healthRestored)
{
Health += healthRestored;
}
}
public class MyCharacterAttributes : DependencyObject
{
public static DependencyProperty HealthDependency =
DependencyProperty.Register("HealthValue",
typeof(Double),
typeof(MyCharacterAttributes),
new PropertyMetadata(100.0, HealthAttributeDependencyChanged));
public double HealthValue
{
get
{
return (Double)GetValue(HealthDependency);
}
set
{
SetValue(HealthDependency, value);
}
}
public List<BindingExpressionBase> Bindings { get; set; }
public MyCharacterAttributes()
{
Bindings = new List<BindingExpressionBase>();
}
private static void HealthAttributeDependencyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
}
}
Las cosas más importantes a tener en cuenta aquí son la herencia de DependencyObject y la implementación de DependencyProperty .
En la práctica, entonces, lo que sucede es lo siguiente. Creé un formulario WPF simple y configuré el siguiente código:
MyCharacter Character { get; set; }
MyCharacterAttributes CharacterAttributes = new MyCharacterAttributes();
public MainWindow()
{
InitializeComponent();
Character = new MyCharacter();
CharacterAttributes = new MyCharacterAttributes();
// Set up the data binding to point at Character (Source) and
// Property Health (via the constructor argument for Binding)
var characterHealthBinding = new Binding("Health");
characterHealthBinding.Source = Character;
characterHealthBinding.NotifyOnSourceUpdated = true;
characterHealthBinding.NotifyOnTargetUpdated = true;
characterHealthBinding.Mode = BindingMode.TwoWay;
characterHealthBinding.IsAsync = true;
// Now we bind any changes to CharacterAttributes, HealthDependency
// to Character.Health via the characterHealthBinding Binding
var bindingExpression =
BindingOperations.SetBinding(CharacterAttributes,
MyCharacterAttributes.HealthDependency,
characterHealthBinding);
// Store the binding so we can look it up if necessary in a
// List<BindingExpressionBase> in our CharacterAttributes class,
// and so it "lives" as long as CharacterAttributes does, too
CharacterAttributes.Bindings.Add(bindingExpression);
}
private void HitChracter_Button(object sender, RoutedEventArgs e)
{
CharacterAttributes.HealthValue -= 10.0;
}
private void DrinkHealth_Button(object sender, RoutedEventArgs e)
{
Character.DrinkHealthPotion(20.0);
}
Al hacer clic en el botón HitCharacter, la propiedad CharacterAttributes.HealthValue
disminuye en 10. Esto desencadena un evento que, a través del enlace que configuramos anteriormente, también resta 10.0 del valor Character.Health
. Al presionar el botón DrinkHealth, se restaura Character.Health
por 20.0 y también aumenta CharacterAttributes.HealthValue
por 20.0.
También tenga en cuenta que esto está incluido en el marco de la interfaz de usuario: FrameworkElement
(que hereda de UIElement
) tiene SetBinding
y GetBinding
implementados en él. Lo cual tiene sentido: ¡los elementos de GUI de enlace de datos son un escenario perfectamente válido para las interfaces de usuario! Sin embargo, si mira más profundo, SetValue
, por ejemplo, simplemente llama a BindingOperations.SetBinding
en una interfaz interna, por lo que podemos implementarlo sin tener que usar un UIElement
(como se muestra en el ejemplo anterior). Sin embargo, la única dependencia que tenemos que transferir es DependencyObject
y DependencyProperty
; estas son obligatorias para que DataBinding funcione, pero, siempre que tus objetos hereden de DependencyObject
, no necesitas ir a ninguna parte cerca de un cuadro de texto: - )
La desventaja, sin embargo, es que algunas de las cosas vinculantes se han implementado a través de métodos internal
, por lo que puede encontrar escenarios donde las acciones vinculantes que desea implementar pueden requerir que escriba código adicional porque simplemente no puede acceder a implementaciones de marcos como las clases nativas pueden. Sin embargo, el enlace de datos TwoWay como el ejemplo anterior es perfectamente posible, como se ha demostrado.
Técnicamente, ¿cómo funcionan los motores de enlace de datos bajo el capó? Especialmente, ¿cómo se ve el mecanismo del "sincronizador" en el enlace de datos y cómo funciona?
En muchos marcos como .NET, Java, Flex, etc., proporcionan un motor de enlace de datos. He estado usando las llamadas API, así que todo es fácil para mí, ya que todo lo que tengo que hacer es hacer llamadas a la API.
Ahora, estoy interesado en quizás intentar escribir un motor de enlace de datos relativamente simple para un juego en el que estoy trabajando. Aunque estoy usando C #, tengo razones para no poder usar el WinForms integrado y el motor de enlace de datos (ver la información de antecedentes a continuación para conocer el motivo). Como no puedo usar el motor de enlace de datos existente en C #, pensé que tendría que escribir uno por mi cuenta. Por lo tanto, necesito saber los detalles esenciales de cómo el enlace de datos generalmente funciona bajo el capó. Con esto, no me refiero a cómo usar el enlace de datos en C #. Quiero decir, ¿cómo funciona el enlace de datos interna y arquitectónicamente?
Traté de buscar en la web tutoriales y artículos sobre el enlace de datos, pero la mayoría de los resultados me vinieron a la mente como cómo usar el enlace de datos existente en C #, que no es lo que quiero.
Entonces, antes de poder comenzar a planear la creación de mi propia carpeta de datos, pensé que necesitaría saber cómo funcionan los motores de enlace de datos bajo el capó. Y aún más importante, ¿cómo se ve y funciona el mecanismo del "sincronizador" en el motor de enlace de datos, es decir, cómo se mantienen sincronizados los datos todo el tiempo, ya sea en un enlace de ida o de dos vías?
Un poco de información general de por qué hice esta pregunta:
Hace un tiempo, formulé una pregunta sobre cómo podría utilizar el enlace de datos en C # para las IU que no están utilizando WinForms estándar. La respuesta que obtuve fue que el motor de enlace de datos en C # está estrechamente relacionado con la interfaz de usuario WPF / Windows Forms. Entonces, supongo que no puedo usar el motor de enlace de datos existente en C #, y probablemente tenga que crear uno solo. El propósito de esto es para un juego y estoy trabajando en eso. Los juegos generalmente tienen su propia interfaz de usuario personalizada (que no es WinForm). Mi intención es configurar un diseño MVVM para la interfaz de usuario y los objetos del juego dentro del juego.
Es una idea bastante simple, pero no necesariamente simple de implementar. Necesita notificación de eventos de 2 vías. Su objeto modelo notifica el marco de enlace de datos cuando cambia, y la interfaz de usuario notifica al marco de enlace de datos de cualquier interacción del usuario.
En el lado del modelo, esto significa escribir sus modelos para notificar cualquier cambio en las propiedades (por ejemplo, implementar la interfaz INotifyPropertyChanged ) y los cambios en las colecciones (por ejemplo, usar ObservableColleciton ). En el lado de la interfaz de usuario, puede conectarse a los eventos proporcionados por el sistema de interfaz de usuario.
Si no desea cambiar su modelo (es decir, desea que el enlace de datos funcione en POCO), entonces necesitaría algún activador para indicarle al sistema de enlace de datos que verifique el modelo en busca de cambios usando la reflexión. Probablemente llamarás esto manualmente cada vez que tu código cambie el modelo.
Después de eso, solo está sondeando todos los eventos, y es probable que se convierta en un desastre ya que se necesita una biblioteca de diferentes tipos de objetos vinculantes que conecten varios tipos de datos a varios tipos de IU.
Puede valer la pena revisar la documentación para knockout.js, http://knockoutjs.com/ , obviamente es una solución web, pero los principios son los mismos y entra en muchos detalles sobre los componentes que están en la biblioteca que en principio será muy similar a los componentes de cualquier sistema.
La parte "Vida antes de encuadernar" en esta publicación me resultó más fácil para entender cómo se puede crear la unión bidireccional.
La idea es la misma que describe James . Lanzas un evento cuando se llama a un setter de propiedad. Pero lo haces solo si el valor de la propiedad ha cambiado. Entonces te suscribes al evento. Y en el suscriptor cambia una propiedad dependiente. Para la propiedad dependiente, usted hace lo mismo (para obtener vinculaciones bidireccionales). Este esquema no muere con el desbordamiento de la pila, ya que setter regresa al instante si el valor no ha cambiado.
Reduje el código en la publicación a esta implementación manual de enlace bidireccional:
static void Main()
{
var ui = new Ui();
var model = new Model();
// setup two-way binding
model.PropertyChanged += (propertyName, value) =>
{
if (propertyName == "Title")
ui.Title = (string) value;
};
ui.PropertyChanged += (propertyName, value) =>
{
if (propertyName == "Title")
model.Title = (string) value;
};
// test
model.Title = "model";
Console.WriteLine("ui.Title = " + ui.Title); // "ui.Title = model"
ui.Title = "ui";
Console.WriteLine("model.Title = " + model.Title);// "model.Title = ui"
Console.ReadKey();
}
}
public class Ui : Bindable
{
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title == value) return;
_title = value;
OnChange("Title", value); // fire PropertyChanged event
}
}
}
public class Model : Bindable
{
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title == value) return;
_title = value;
OnChange("Title", value); // fire PropertyChanged event
}
}
}
public class Bindable
{
public delegate void PropertyChangedEventHandler(
string propertyName, object value);
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChange(string propertyName, object value)
{
if (PropertyChanged != null)
PropertyChanged(propertyName, value);
}
}
Puede utilizar aspectos (PostSharp, por ejemplo) para interceptar llamadas de establecimiento de propiedades y, por lo tanto, deshacerse de los campos de respaldo. Tus clases se verán así:
public class Ui : Bindable
{
[Bindable]
public string Title { get; set; }
[Bindable]
public string Name { get; set; }
}
Y utilizando la reflexión puede reducir el código de enlace a solo:
Binder.Bind(() => ui.Title, () => model.Title);
Binder.Bind(() => ui.Name, () => model.Name);
Mi prueba de concepto: https://gist.github.com/barsv/46650cf816647ff192fa