Hola a todos!
Por varias razones, la mayoría de nosotros usamos aplicaciones de escritorio, al menos un navegador :) Y algunos de nosotros necesitamos escribir las nuestras. En este artículo, quiero repasar el proceso de desarrollo de una aplicación de escritorio simple usando la tecnología Windows Presentation Foundation (WPF) y aplicando el patrón MVVM. Aquellos que deseen continuar leyendo, por favor, debajo del gato.
Creo que no es necesario decir que WPF es el desarrollo de Microsoft :) Esta tecnología está diseñada para desarrollar aplicaciones de escritorio para Windows, comenzando con Windows XP. Por qué Esto se debe al hecho de que WPF se ejecuta sobre la plataforma .NET, cuyos requisitos mínimos son Windows XP y versiones posteriores. Desafortunadamente, WPF no funciona en otras plataformas, aunque hay posibilidades de que esto cambie en el futuro cercano: el marco de Avalonia basado en WPF está en desarrollo .
¿Qué tiene de especial WPF?
Dos diferencias principales entre WPF y otras herramientas de creación de escritorio:
- El lenguaje de marcado XAML para marcar la interfaz de la ventana.
- Renderizado a través de DirectX, aceleración de gráficos de hardware.
No voy a entrar en detalles, porque Este no es el tema del artículo. Si está interesado, entonces google XAML, WPF rendering, milcore.dll y DirectX :)
¿De qué trata este artículo?
Este artículo contiene una aplicación de ejemplo basada en la tecnología WPF:
Trataré de orientar el material del artículo en una dirección práctica al estilo de "repetir después de mí" con explicaciones.
¿Qué necesitamos para repetir el artículo?
Poca experiencia de desarrollo en C # :) Como mínimo, debe comprender bien la sintaxis del lenguaje. También necesitará una máquina Windows (Win 10 en los ejemplos) con Visual Studio instalado (en los ejemplos será 2017, hay una versión comunitaria gratuita). Al instalar VS, deberá habilitar el soporte para el desarrollo de escritorio para la plataforma .NET

También en esta sección describiré la creación de un proyecto.
Lanzamos VS, creamos un nuevo proyecto, seleccionamos el tipo de aplicación WPF App (.NET Framework) (puede ingresarlo en la barra de búsqueda en la esquina superior derecha), llámelo como quiera.

Después de crear un nuevo proyecto, se abre la ventana del editor de la interfaz, así me parece

En la parte inferior hay un editor de diseño, en la parte superior hay una vista previa de la interfaz de la ventana, pero puede cambiar la ubicación relativa del editor de código y la vista previa de la interfaz para que se ubiquen en orden horizontal utilizando estos botones (en el borde de las dos áreas a la derecha):

Antes de empezar
Los elementos de la ventana (también se denominan controles de la palabra Control ) deben colocarse dentro del contenedor o dentro de otro elemento del tipo ContentControl. Un contenedor es un control especial que le permite colocar varios controles secundarios dentro y organizar su disposición mutua. Ejemplos de contenedores:
- Cuadrícula : le permite organizar elementos por columnas y filas, el ancho de cada columna o fila se configura individualmente.
- StackPanel : le permite organizar a los niños en una sola fila o columna.
Hay otros contenedores. Como el contenedor también es un control, dentro del contenedor puede haber contenedores anidados que contengan contenedores anidados, etc. Esto le permite organizar de manera flexible los controles entre sí. Además, con la ayuda de los contenedores, no podemos controlar de manera menos flexible el comportamiento de los controles anidados al cambiar el tamaño de una ventana.
Interfaz MVVM e INotifyPropertyChanged. Copia del texto.
El resultado de este ejemplo será una aplicación con dos controles, en uno de los cuales puede editar el texto, y en el otro solo ver. Los cambios de uno a otro se transferirán sincrónicamente sin una copia explícita del texto mediante el enlace .
Entonces, tenemos un proyecto recién creado (lo llamé Ex1 ), vaya al editor de diseño y, en primer lugar, reemplace el contenedor predeterminado ( <Grid> </Grid> ) con <StackPanel> </StackPanel> . Este contenedor será suficiente, porque tendremos que colocar solo dos controles uno encima del otro. Especificamos explícitamente cómo se organizarán los componentes agregando la propiedad Orientación = "Vertical" . Agregue un par de elementos dentro de la pila del panel: un campo para ingresar texto y un campo para mostrar texto. Dado que estos controles no contendrán código incrustado, puede describirlos con una etiqueta de cierre automático (consulte el código a continuación). Después de todos los procedimientos anteriores, el código de descripción del contenedor y los controles anidados deben adoptar la siguiente forma:
<StackPanel Orientation="Vertical"> <TextBox /> <TextBlock /> </StackPanel>
Ahora centrémonos en el propósito de este ejemplo. Queremos que al escribir en el cuadro de texto, el mismo texto se muestre sincrónicamente en el bloque de texto, evitando la operación de copia explícita. Necesitamos algún tipo de entidad de conexión, y aquí llegamos a algo como la vinculación , que se mencionó anteriormente. La vinculación en la terminología de WPF es un mecanismo que le permite asociar algunas propiedades de los controles con algunas propiedades de un objeto de clase C # y actualizar mutuamente estas propiedades cuando cambia una de las partes del paquete (esto puede funcionar en una, la otra o en ambas direcciones a la vez). Para aquellos que están familiarizados con Qt, puede dibujar una analogía de ranuras y señales. Para no alargar el tiempo, pasemos al código.
Por lo tanto, para organizar el enlace, necesita las propiedades de los controles y alguna propiedad de una determinada clase de C #. Primero, descubramos el código XAML. El texto de ambos controles se almacena en la propiedad Texto, por lo tanto, agregue un enlace para estas propiedades. Se hace así:
<TextBox Text="{Binding}"/> <TextBlock Text="{Binding}"/>
Hicimos un enlace, pero por ahora no está claro por qué :) Necesitamos un objeto de alguna clase y alguna propiedad en este objeto al que se realizará el enlace (como dicen, al que debe unirse).
Entonces, ¿qué es esta clase? Esta clase se llama un modelo de vista y sirve como un enlace entre la vista (interfaz o sus partes) y el modelo (modelo, es decir, aquellas partes del código que son responsables de la lógica de la aplicación. Esto le permite separar (hasta cierto punto) ) la lógica de la aplicación desde la interfaz (vista) se denomina patrón Modelo-Vista-Modelo de Vista (MVVM) En WPF, esta clase también se denomina DataContext .
Sin embargo, escribir una clase de modelo de vista no es suficiente. Es necesario informar de alguna manera al mecanismo de enlace que la propiedad del modelo de vista o la propiedad de vista ha cambiado. Para hacer esto, hay una interfaz especial INotifyPropertyChanged , que contiene el evento PropertyChanged . Implementamos esta interfaz en el marco de la clase base BaseViewModel . En el futuro, heredaremos todos nuestros modelos de vista de esta clase base para no duplicar la implementación de la interfaz. Por lo tanto, agregue el directorio ViewModels al proyecto y agregue el archivo BaseViewModel.cs a este directorio. Obtenemos la siguiente estructura de proyecto:

Código de implementación para el modelo de vista base:
using System.ComponentModel; namespace Ex1.ViewModels { public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
Creemos nuestro modelo de vista para nuestra clase MainWindow , heredando de la base. Para hacer esto, en el mismo directorio ViewModels , cree el archivo MainWindowViewModel.cs , dentro del cual habrá dicho código:
namespace Ex1.ViewModels { public class MainWindowViewModel : BaseViewModel { } }
Genial Ahora tenemos que agregar una propiedad a este modelo de vista, en el que vincularemos el texto de nuestros controles. Como se trata de texto, el tipo de esta propiedad debe ser una cadena :
public string SynchronizedText { get; set; }
Como resultado, obtenemos dicho código
namespace Ex1.ViewModels { public class MainWindowViewModel : BaseViewModel { public string SynchronizedText { get; set; } } }
Entonces, parece que lo hicieron. Queda por vincularse en esta propiedad desde la vista y está listo. Hagámoslo ahora mismo:
<TextBox Text="{Binding Path=SynchronizedText}"/> <TextBlock Text="{Binding Path=SynchronizedText}"/>
Nishtyak, comenzamos el proyecto, escribimos en el cuadro de texto iiiii ... no pasa nada))) Bueno, está bien, de hecho vamos por el camino correcto, simplemente no hemos llegado al punto correcto.
Propongo parar por un momento y pensar en lo que nos estamos perdiendo. Tenemos una vista Viewmodel también. Propiedades como zabindili. Se implementa la interfaz deseada. Trabajamos mucho para copiar una línea de texto patética, ¿por qué necesitamos esto?
De acuerdo, bromas a un lado. Olvidamos crear un objeto modelo de vista y algo más (más sobre eso más adelante). Describimos la clase en sí, pero esto no significa nada, porque no tenemos objetos de esta clase. Ok, ¿dónde necesitas almacenar un enlace a este objeto? Más cerca del comienzo del ejemplo, mencioné un cierto DataContext utilizado en WPF. Por lo tanto, cualquier vista tiene una propiedad DataContext a la que podemos asignar un enlace a nuestro modelo de vista. Hagámoslo Para hacer esto, abra el archivo MainWindow.xaml y presione F7 para abrir el código de esta vista. Está casi vacío, solo tiene un constructor de clase de ventana. Agregue la creación de nuestro modelo de vista y colóquelo en el DataContext de la ventana (no olvide agregar usando con el espacio de nombres deseado):
public MainWindow() { InitializeComponent(); this.DataContext = new MainWindowViewModel(); }
Era simple, pero aún no era suficiente. Aún así, cuando se inicia la aplicación, no se produce sincronización de texto. ¿Qué más hay que hacer?
Debe generar el evento PropertyChanged cuando cambie la propiedad SynchronizedText e informar a la vista que debe supervisar este evento. Entonces, para activar el evento, modifique el código del modelo de vista:
public class MainWindowViewModel : BaseViewModel { private string _synchronizedText; public string SynchronizedText { get => _synchronizedText; set { _synchronizedText = value; OnPropertyChanged(nameof(SynchronizedText)); } } }
Que hemos hecho aqui Agregamos un campo oculto para almacenar texto, lo envolvimos en una propiedad existente y, al cambiar esta propiedad, no solo cambiamos el campo oculto, sino que también llamamos al método OnPropertyChanged definido en el modelo de vista base y generamos el evento PropertyChanged declarado en la interfaz INotifyPropertyChanged , también implementado en la base Ver modelos. Resulta que cada vez que el texto cambia, se produce el evento PropertyChanged , al que se pasa el nombre de la propiedad del modelo de vista que se cambió.
Bueno, casi todo, la línea de meta! Queda por especificar la vista que debería escuchar el evento PropertyChanged :
<TextBox Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"/> <TextBlock Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
Además del hecho de que indicamos por qué disparador debería llevarse a cabo la actualización, también indicamos en qué dirección se está rastreando esta actualización: de vista a vista del modelo o viceversa. Como ingresamos texto en el cuadro de texto, solo nos interesan los cambios en la vista, por lo que seleccionamos el modo OneWayToSource . En el caso del bloque de texto, todo es exactamente lo contrario: estamos interesados en los cambios en el modelo de vista para mostrarlos en la vista, por lo que seleccionamos el modo OneWay . Si quisiéramos rastrear los cambios en ambas direcciones, no podríamos especificar el Modo en absoluto, o especificar TwoWay explícitamente.
Entonces, ejecute el programa, escriba el texto y ¡voi-la! ¡El texto cambia sincrónicamente y no copiamos nada en ningún lado!

Gracias por su atención, continuará. Nos ocuparemos de DataTemplate y el patrón Command.