2009-11-18 10 views
9

Estoy usando el patrón MVVM en mi primera aplicación WPF y tengo un problema con algo bastante básico, supongo.Actualizar la interfaz de usuario de la clase ViewModel (patrón MVVM) en WPF

Cuando el usuario pulsa el botón "guardar" en mi vista, se ejecuta un comando que llama al vacío privado Guardar() en mi ViewModel.

El problema es que el código en "Guardar()" tarda un poco en ejecutarse, por lo que me gustaría ocultar el botón "Guardar" en la vista de la IU antes de ejecutar la gran cantidad de código.

El problema es que la vista no se actualiza hasta que todo el código se ejecuta en el modelo de vista. ¿Cómo puedo forzar la vista para volver a dibujar y procesar los eventos PropertyChanged antes de ejecutar el código Save()?

Además, me gustaría una forma reutilizable, para que también pueda hacer lo mismo en otras páginas. ¿Alguien más ya hizo algo como esto? Un mensaje de "Cargando ..."?

Respuesta

11

Si lleva mucho tiempo, considere usar un hilo separado, por ejemplo usando un BackgroundWorker, para que el subproceso de la interfaz de usuario pueda permanecer receptivo (es decir, actualice la UI) mientras se realiza la operación.

En el método de Save, lo haría

  • cambio de la interfaz de usuario (es decir, modificar algunos INotifyPropertyChanged o DependencyProperty IsBusySaving booleano que está ligado a la interfaz de usuario, se esconde en el botón Guardar y tal vez muestra alguna barra de progreso con IsIndeterminate = True) y
  • start a BackgroundWorker.

En el controlador de eventos DoWork de su BackgroundWorker, realiza la operación de guardado prolongada.

En el controlador de eventos RunWorkerCompleted, que se ejecuta en el hilo de la interfaz de usuario, configura IsBusySaving en falso y tal vez cambie otras cosas en la interfaz de usuario para mostrar que ha terminado.

ejemplo de código (no probado):

BackgroundWorker bwSave; 
DependencyProperty IsBusySavingProperty = ...; 

private MyViewModel() { 
    bwSave = new BackgroundWorker(); 

    bwSave.DoWork += (sender, args) => { 
     // do your lengthy save stuff here -- this happens in a separate thread 
    } 

    bwSave.RunWorkerCompleted += (sender, args) => { 
     IsBusySaving = false; 
     if (args.Error != null) // if an exception occurred during DoWork, 
      MessageBox.Show(args.Error.ToString()); // do your error handling here 
    } 
} 

private void Save() { 
    if (IsBusySaving) { 
     throw new Exception("Save in progress -- this should be prevented by the UI"); 
    } 
    IsBusySaving = true; 
    bwSave.RunWorkerAsync(); 
} 
+0

gracias, lo intentaré. –

+0

lo siento, soy un dumbo total cuando se trata de enhebrar. Dentro del código Guardar I (a veces) trato de navegar a otra página. Pero debido a que estoy en otro hilo, esto da un error de tiempo de ejecución. Supongo que tengo que hacer una devolución de llamada al hilo original y navegar desde allí a la otra página. Pero lo intentaré yo mismo, estoy seguro de que no es difícil comunicarme con el hilo original. –

+0

"El hilo que llama no puede acceder a este objeto porque lo posee un hilo diferente". es el mensaje que recibo Si sabes de memoria lo que necesito, házmelo saber :-) –

0

Siempre se puede hacer algo como esto:

public class SaveDemo : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    private bool _canSave; 

    public bool CanSave 
    { 
    get { return _canSave; } 
    set 
    { 
     if (_canSave != value) 
     { 
     _canSave = value; 
     OnChange("CanSave"); 
     } 
    } 
    } 

    public void Save() 
    { 
    _canSave = false; 

    // Do the lengthy operation 
    _canSave = true; 
    } 

    private void OnChange(string p) 
    { 
    PropertyChangedEventHandler handler = PropertyChanged; 
    if (handler != null) 
    { 
     handler(this, new PropertyChangedEventArgs(p)); 
    } 
    } 
} 

entonces se podría enlazar la propiedad IsEnabled del botón a la propiedad CanSave, y lo hará se habilitará/deshabilitará automáticamente. Un método alternativo, y uno que iría sería utilizar el comando CanExecute para ordenar esto, pero la idea es lo suficientemente similar para que usted pueda trabajar.

+1

(1) Si configura _canSave en lugar de CanSave, OnChange no se generará.(2) No creo que funcione, ya que Save se ejecuta en el hilo de UI, por lo que la interfaz de usuario de WPF no se actualizará hasta que Save haya finalizado. – Heinzi

+0

sí, apruebo la respuesta, pero no creo que solucione mi problema. La sugerencia de Heinzi lo solucionó. –

+0

@Heinzi: un buen lugar en CanSave, y sí, funcionará porque el cambio de notificación se produce al inicio de la operación de guardado, por lo tanto, la interfaz de usuario se actualiza en ese momento. –

3

Está utilizando el patrón MVVM, por lo que el comando del botón Guardar se establece en una instancia del objeto RoutedCommand que se agrega a la colección CommandBindings de la ventana, ya sea de manera declarativa o imperativa.

Suponiendo que lo haga declarativamente. Algo así como

<Window.CommandBindings> 
    <CommandBinding 
     Command="{x:Static namespace:ClassName.StaticRoutedCommandObj}" 
     CanExecute="Save_CanExecute" 
     Executed="Save" 
    /> 
</Window.CommandBindings> 

Para el manejador de evento enrutado Ejecutado, su método Save(), en la entrada, se establece una variable a falso, en la declaración se establece de nuevo a la verdadera. Algo como.

void Save(object sender, ExecutedRoutedEventArgs e) 
{ 
    _canExecute = false; 
    // do work 
    _canExecute = true; 
} 

Para el manejador del evento enrutado CanExecute, el método Save_CanExecute(), se utiliza la variable como una de las condiciones.

void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e) 
{ 
    e.CanExecute = _canExecute && _others; 
} 

Espero que esté claro. :)

0

Usted puede lograr esto mediante el siguiente código ..

Thread workerThread = null; 
void Save(object sender, ExecutedRoutedEventArgs e) 
{ 
workerThread = new Thread(new ThreadStart(doWork)); 
SaveButton.isEnable = false; 
workerThread.start(); 
} 

hacer todo su largo proceso de DoWork() método

en algún otro método ..

workerThread.join(); 
SaveButtton.isEnable = true; 

Esto causará que se ejecute un proceso largo en otro hilo y no bloqueará su UI, si desea mostrar una animación mientras el usuario hace clic en el botón Guardar y luego muestra una barra de progreso como iPhone, etc ... denme su opinión, intentaré Ayudarte aún más.

0

Última respuesta, pero pensé que sería bueno ingresar un poco también.

En lugar de crear su propio hilo nuevo, probablemente sería mejor dejarlo en el threadpool para ejecutar el guardado. No lo obliga a ejecutarlo instantáneamente, como crear su propio hilo, pero le permite guardar recursos de enhebrado.

La manera de hacer esto es:

ThreadPool.QueueUserWorkItem(Save); 

El problema con el uso de este enfoque, además, es que usted está obligado a tener su método de "Guardar()" tomar en un objeto que actuará como un estado. Estaba teniendo un problema similar al suyo y decidí seguir este camino porque el lugar en el que estoy trabajando es muy necesitado de recursos.

+0

gracias por su respuesta. Utilicé la respuesta aceptada en mi solicitud y funciona bien. No sé cómo se usa el recurso en comparación con su solución, pero es muy conveniente trabajar con Backgroundworker. –

+0

Keith: En .Net 3.5 y superior, considere expresiones lambda para recortar el objeto de estado si no lo necesita. ThreadPool.QueueUserWorkItem (estado => Guardar()); Tal vez un poco más de sobrecarga aquí, pero el código a menudo se simplifica con menos métodos de apaciguamiento de delegados flotando. También puede usar una lambda para convertir el objeto de estado a lo que necesite y extraer propiedades específicas. ThreadPool.QueueUserWorkItem (state => Guardar (indicar como SaveArgs) .Length, estado como SaveArgs) .TimeStamp); – Gusdor

Cuestiones relacionadas