Cuando desarrollo mis aplicaciones, intento que todas tengan un sistema de mensajería con el que pueda comunicarse cualquier ventana de la aplicación con esta, de modo que generando un mensaje por ejemplo, «cargando caché de datos…» y una barra de progreso indique su estado, aparezca en la barra de estado o mediante un cuadro de dialogo, el mensaje «cargando caché de datos…» y la barra aumente su progreso, de modo que si tengo varias ventanas abiertas, podría visualizar los mismos mensajes en todas y esto mediante un ejemplo os lo voy a mostrar. Para comenzar creo una clase llamada
Message
que contiene cuatro propiedades, infoText
que almacena el texto del mensaje principal, progress
que almacena el valor del progreso, progressIsVisible
que almacena si la barra de progreso es visible y por último infoText2
que almacena un segundo texto por si acaso lo necesito.A esta clase le añado varios eventos, por si acaso los necesito, que son cuando cambia el texto del mensaje o cuando cambia el progreso. Extiendo varios
EventArgs
con EventArgsMessage
para obtener los valores nuevo y antiguos y el valor del progreso EventArgsProgress
. Contiene dos métodos, uno para asignar los valores de la propiedades y otro para borrarlos.Por otra parte creo una colección del tipo
ObservableCollection
en la clase App
de la aplicación de modo que cualquier ventana puede acceder a esta colección si la hacemos pública, la cual contiene un evento CollectionChanged
el cual usaremos en cada ventana para capturar los mensajes.
public static ObservableCollection<Message>; messages = new ObservableCollection<Message>();
Y ahora solo nos queda capturar los eventos en cada ventana. Para ver esto, voy a crear una ventana principal que iniciará un hilo desde un botón con un proceso que suma desde 1 hasta 100 con un retardo de 50 ms y que en cada incremento, actualizará un control Textblock
con el texto del mensaje y una barra de progreso con un valor. Al mismo tiempo, abriré otra ventana en la aplicación con un TextBlock
y otra barra de progreso que deberían actualizarse de igual modo que lo hace la ventana principal y sin albergar ningún código en ella que le permita incrementar nada ni mostrar nada. Para mostrar el mensaje y actualizar las barras uso un delegado en cada ventana que se encargará de hacer este trabajo y para acceder a los controles sin errores, Dispatcher.Invoke(new MessageAddedHandler(setMessage), message)
invocando al delegado y como argumento el nuevo mensaje. ¿Qué resultado obtendremos? Dos ventanas, la que contiene el botón y la que no. Al pulsar se inicia la carga y en ambas ventanas se actualizan tanto el texto como el valor de la barra de progreso al mismo tiempo.
Pasamos al código.
Clase Message
using System; namespace WpfApplication1 { public class Message { #region Constructor public Message() { this.infoText = ""; this.progress = 0; this.progressIsVisible = false; } public Message(string _text, bool _isVisible, int _progress) { setMessage(_text, _isVisible, _progress); } #endregion #region Properties private string _infotext; private int _progress; public int progress { get { return _progress; } set { if (_progress!=value) { } _progress = value; } } public string infoText { get { return _infotext; } set { if (_infotext!=value) { EventArgsMessage eventArgsMessage = new EventArgsMessage(_infotext,value); } _infotext = value; } } public bool progressIsVisible { get; set; } public string infoText2 { get; set; } #endregion #region Events public event EventHandler<eventargsmessage> ChangedTextEventHandler; public event EventHandler<eventargsprogress> ChangedProgressEventHandler; #endregion #region Private Methods void OnChangedText(EventArgsMessage e) { if (ChangedTextEventHandler!=null) { ChangedTextEventHandler(this, e); } } void OnChangedProgress(EventArgsProgress e) { if (ChangedProgressEventHandler!=null) { ChangedProgressEventHandler(this, e); } } #endregion #region Public Methods /// <summary> /// Asigna los valores al mensaje /// </summary> /// <param name="_text" />Texto del mensaje /// <param name="_isVisible" />Visibilidad del mensaje /// <param name="_progress" />Progreso public void setMessage(string _text, bool _isVisible, int _progress) { this.infoText = _text; this.progressIsVisible = _isVisible; this.progress = _progress; } /// <summary> /// Borra los valores del mensaje /// </summary> public void Clear() { setMessage("", false, 0); } #endregion } public class EventArgsMessage:EventArgs { public EventArgsMessage() { } public EventArgsMessage (string _oldText, string _newText) { this.oldText = oldText; this.newText = _newText; } public string oldText { get; set; } public string newText { get; set; } } public class EventArgsProgress : EventArgs { public EventArgsProgress() { } public EventArgsProgress(int _newValue) { this.newValue = _newValue; } public int newValue { get; set; } } } Clase <code>App</code> public partial class App : Application { public static ObservableCollection<message> messages = new ObservableCollection<message>(); public App() { } }
Ventana principal con método de cálculo
Constructor
public MainWindow() { InitializeComponent(); App.messages.CollectionChanged += Messages_CollectionChanged; Window1 w1 = new Window1(); w1.Show(); }
Propiedades. El delegado, un objeto CancellationTokenSource
para cancelar el proceso y un flag para saber si la app está corriendo o no.
delegate void MessageAddedHandler(Message _message); CancellationTokenSource cs; public bool isRunning { get; set; } = false;
Captura del evento. Ocurre cuando la colección cambia.
void Messages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action==System.Collections.Specialized.NotifyCollectionChangedAction.Add) { foreach (var item in e.NewItems) { Dispatcher.Invoke(new MessageAddedHandler(setMessage), (Message)item); } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) { Dispatcher.Invoke(new MessageAddedHandler(setMessage), new Message()); } }
Métodos. El primer método asigna un mensaje a los controles, el segundo inicia el proceso o lo cancela y el tercero suma 1 después de 50 ms: una vez que acaba, finaliza y borra los mensajes.
void setMessage(Message message) { this.textBlock.Text = message.infoText; this.pb.Value = message.progress; this.pb.Visibility = message.progressIsVisible ? Visibility.Visible : Visibility.Collapsed; } private void button_Click(object sender, RoutedEventArgs e) { if (isRunning) { this.button.Content = "Iniciar"; cs.Cancel(); } else { isRunning = true; cs = new CancellationTokenSource(); this.button.Content = "Cancelar"; var t = Task.Factory.StartNew(() => doSomeThing(cs.Token),cs.Token); } } void doSomeThing(CancellationToken ct) { try { for (int i = 0; i <= 100; i++) { ct.ThrowIfCancellationRequested(); Thread.Sleep(50); App.messages.Add(new Message(String.Format("Cargando {0}", i), true, i)); } App.messages.Clear(); } catch (OperationCanceledException ex) { isRunning = false; App.messages.Clear(); return; } }
Ventana que captura mensajería. En esta ventana solo se captura el evento cuando la colección cambia y la asignación de los valores al mensaje.
public partial class Window1 : Window { public Window1() { InitializeComponent(); App.messages.CollectionChanged += Messages_CollectionChanged; ; } delegate void MessageAddedHandler(Message _message); private void Messages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { foreach (var item in e.NewItems) { Dispatcher.Invoke(new MessageAddedHandler(setMessage), (Message)item); } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) { Dispatcher.Invoke(new MessageAddedHandler(setMessage), new Message()); } } void setMessage(Message message) { this.textBlock.Text = message.infoText; this.pb.Value = message.progress; this.pb.Visibility = message.progressIsVisible ? Visibility.Visible : Visibility.Collapsed; } } }
y con todo funcionando, cada vez que iniciemos desde la ventana principal el proceso, las dos ventanas deben mostrar el mismo texto y el mismo progreso.Os dejo el proyecto completo en el enlace.