none
MVVM - View Não Atualiza. RRS feed

  • Pergunta

  • Boa Tarde,

    Estou trabalhando num exemplo em Silverlight utilizando MVVM para homologar o padrão para utilizar-mos na empresa onde trabalho, mas estou com alguns problemas.

    Este é o codigo que eu utilizei para o exemplo:

    <UserControl x:Class="View.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:ViewModel="clr-namespace:ViewModel;assembly=ViewModel"
      mc:Ignorable="d"
      d:DesignHeight="300" d:DesignWidth="400">
    
      <Grid x:Name="LayoutRoot" Background="White">
        <Grid.DataContext>
          <ViewModel:PessoaVM/>
        </Grid.DataContext>
        
        <StackPanel HorizontalAlignment="Left">
          <TextBlock Text="Nome:"
                Margin="10,10,0,0"/>
          <TextBox Text="{Binding Nome, 
                      Mode=TwoWay, 
                      NotifyOnValidationError=True, 
                      ValidatesOnExceptions=True}" 
               Width="100"
               Height="23"
               Margin="10,0,10,10"/>
          
          <TextBlock Text="Idade:"
                Margin="10,10,10,0"/>
          <TextBox Text="{Binding Idade, 
                      Mode=TwoWay, 
                      NotifyOnValidationError=True, 
                      ValidatesOnExceptions=True}" 
               Width="100"
               Height="23" 
               Margin="10,0,10,10"/>
          
          <Button Content="Mensagem" 
              Command="{Binding DelegadoExecutarAdicionaIdade}" 
              CommandParameter="{Binding}"
              IsEnabled="{Binding delegadoExecutarAdicionaIdade.canExecuteCache, 
                        Mode=TwoWay}"
              Width="100" 
              Height="25"
              Margin="10,10,10,0"/>
        </StackPanel>
      </Grid>
    </UserControl>
    
    

    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.ComponentModel;
    using Model;
    
    namespace ViewModel
    {
      public class PessoaVM
      {
        private Pessoa pessoa;
        private DelegateCommand delegadoExecutarAdicionaIdade;
    
    		public string Nome 
        {
          get { return this.pessoa.Nome; }
          set
          {
            pessoa.Nome = value;
          }
        }
        public int Idade
        {
          get { return this.pessoa.Idade; }
          set
          {
            pessoa.Idade = value;
          }
        }
        public DelegateCommand DelegadoExecutarAdicionaIdade
        {
          get { return this.delegadoExecutarAdicionaIdade; }
        }
    
        public PessoaVM()
        {
          pessoa = new Pessoa();
          delegadoExecutarAdicionaIdade = new DelegateCommand(pessoa.AdicionaIdade, pessoa.VerificaAdicionaIdade);
        }
      }
    }
    
    /* -------------------------------------------------- */
    
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    
    namespace ViewModel
    {
      public class DelegateCommand : ICommand
      {
        private Func<object, bool> canExecute;
        private Action<object> executeAction;
    
        public bool canExecuteCache = false;
        public event EventHandler CanExecuteChanged;
    		
    		public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute)
        {
          this.canExecute = canExecute;
          this.executeAction = executeAction;
        }
    
        public bool CanExecute(object parameter)
        {
          if (canExecuteCache != canExecute(parameter))
          {
            canExecuteCache = !canExecuteCache;
            CanExecuteChanged(this, new EventArgs());
          }
          return canExecuteCache;
        }
    
        public void Execute(object parameter)
        {
          executeAction(parameter);
          canExecute(parameter);
        }
      }
    }
    
    /* -------------------------------------------------- */
    
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.ComponentModel;
    
    namespace Model
    {
      public class Pessoa : INotifyPropertyChanged
      {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
        {
          if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    
        private string nome;
        private int idade;
    
        public string Nome 
        {
          get { return this.nome; }
          set
          {
            if (value.Length > 3)
            {
              this.nome = value;
              OnPropertyChanged("Nome");
            }
            else throw new Exception("Nome muito curto! Deve ser maior que 3 caracteres.");
          }
        }
        public int Idade
        {
          get { return this.idade; }
          set
          {
            this.idade = value;
            OnPropertyChanged("Idade");
          }
        }
    
        public void AdicionaIdade(object parameter)
        {
          if (this.Idade < 25) Idade++;
          else throw new Exception("Idade Maior que 25");
        }
    
        public bool VerificaAdicionaIdade(object parameter)
        {
          if (this.Idade < 25) return true;
          else return false;
        }
      }
    }
    

    O problema que ainda não consegui resolver é que ele realiza toda a operação corretamente e guarda o valor da idade somada pelo método, mas nunca notifica a mudança de valor para a tela se atualizar. O Botão se desabilita normalmente  quando Idade passa de 25, mas nem a exceção que eu inseri no método AdicionaIdade chega na view. Já a excessão do set da propriedade Nome, quando eu atualizo com um valor invalido, seja pela view ou pela viewmodel, aparece num balão vermelho ao lado do textbox de nome.

    Alguem pode me ajudar a resolver o problema?
    quarta-feira, 3 de agosto de 2011 18:55

Respostas

  • O Milton está correto, mas acho que é legal explicar o motivo. O problema do seu código é que você implementou INotifyPropertyChanged na sua classe model, mas o correto é implementar na ViewModel pois é ela que será utilizada no bindind e a implementação dessa interface só interessa para o bindind saber que o valor da propriedade mudou. Suas classes model só precisam implementar INotifyPropertyChanged se você for utilizá-las diretamente em bindind na interface.

    A questão da exception já é diferente. O balão vermelho em torno do campo só aparece quando ocorreu um erro de binding. Como o seu campo nome tem binding de duas vias com a propriedade Nome, quando o valor do campo muda o set da property é chamado e se ocorrer alguma exception a engine de binding muda o visual do campo texto para exibir o visual de campo com erro. Como para adicionar a idade não está sendo utilizada a engine de binding mas sim um command, este não tem como saber a qual campo se refere o comando e por isso não tem como mudar seu estado para o estado de campo com erro. Seria necessário que você mudasse o state do campo manualmente para o state de erro sempre que o comando for chamado e não funcionar, mas isso foge do principio de separação de responsabilidade que é a principal proposta do MVVM. Outra alternativa seria colocar essa verificação da idade no set da propriedade Idade e ao invés de usar um TextBox você pode usar um controle NumericUpDown do Silverlight Toolkit (http://silverlight.codeplex.com/releases/view/43528) e fazer o binding entre a propriedade value e sua property Idade diretamente, eliminando a necessidade de usar comando para essa ação.


    Atenciosamente,

    Kelps Leite de Sousa | MVP Silverlight
    blog: http://kelps.net
    twitter : http://twitter.com/kelps

    Não se esqueça de "marcar como resposta" o ítem que lhe ajudou.
    • Marcado como Resposta jpovoas sexta-feira, 12 de agosto de 2011 12:25
    segunda-feira, 8 de agosto de 2011 12:42
    Moderador

Todas as Respostas

  • Amigo,

    Acho q na sua classe PessoaVM derveria ter algo assim:

     

     public int Idade
        {
          get { return this.pessoa.Idade; }
          set
          {
            pessoa.Idade = value;
            OnPropertyChanged("Idade");

          }
        }

    E a sua PessoaVM deveria implementar INotifyPropertyChanged


    Milton
    sexta-feira, 5 de agosto de 2011 12:20
  • O Milton está correto, mas acho que é legal explicar o motivo. O problema do seu código é que você implementou INotifyPropertyChanged na sua classe model, mas o correto é implementar na ViewModel pois é ela que será utilizada no bindind e a implementação dessa interface só interessa para o bindind saber que o valor da propriedade mudou. Suas classes model só precisam implementar INotifyPropertyChanged se você for utilizá-las diretamente em bindind na interface.

    A questão da exception já é diferente. O balão vermelho em torno do campo só aparece quando ocorreu um erro de binding. Como o seu campo nome tem binding de duas vias com a propriedade Nome, quando o valor do campo muda o set da property é chamado e se ocorrer alguma exception a engine de binding muda o visual do campo texto para exibir o visual de campo com erro. Como para adicionar a idade não está sendo utilizada a engine de binding mas sim um command, este não tem como saber a qual campo se refere o comando e por isso não tem como mudar seu estado para o estado de campo com erro. Seria necessário que você mudasse o state do campo manualmente para o state de erro sempre que o comando for chamado e não funcionar, mas isso foge do principio de separação de responsabilidade que é a principal proposta do MVVM. Outra alternativa seria colocar essa verificação da idade no set da propriedade Idade e ao invés de usar um TextBox você pode usar um controle NumericUpDown do Silverlight Toolkit (http://silverlight.codeplex.com/releases/view/43528) e fazer o binding entre a propriedade value e sua property Idade diretamente, eliminando a necessidade de usar comando para essa ação.


    Atenciosamente,

    Kelps Leite de Sousa | MVP Silverlight
    blog: http://kelps.net
    twitter : http://twitter.com/kelps

    Não se esqueça de "marcar como resposta" o ítem que lhe ajudou.
    • Marcado como Resposta jpovoas sexta-feira, 12 de agosto de 2011 12:25
    segunda-feira, 8 de agosto de 2011 12:42
    Moderador