I'm new with MVVM Pattern.
I was wondering why everytime my TextChanged() event is fired, the binded IsEnabled() property doesn't change its state. The TextChanged() event is calling IsValid() to check for data validation.
I have this simple ViewModel class
public class myViewModel : ViewModel
{
public bool IsOk { get; set; }
public RelayCommand OnValidateExecute { get; set; }
public myViewModel()
{
OnValidateExecute = new RelayCommand(p => IsValid(), p => true);
}
public bool IsValid()
{
// other codes
IsOk = MethodName();
NotifyPropertyChanged("IsOk");
}
}
I set a breakpoint on IsValid() and the code is working fine. I was wondering why IsEnabled property is not working as expected.
This is my XAML code
<TextBox ...other propeties here....>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding OnValidateExecute}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button x:Name="btnSave" IsEnabled="{Binding IsOk, Mode=TwoWay}"
...other propeties here....>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding OnSaveExecute}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
What I want is that when IsOk property is false, the button should be Disabled, otherwise, Enabled.
Is there some wrong with my data binding? If this question has been asked before, kindly help me redirect on to it.
UPDATE 1
Another problem I have encounter with this code is that, the IsValid() function is triggered first before the setting of value on the textbox. Here's an example, Assuming that the starting value of the textbox is 0, when I changed it to, let's say, 9, the value that will be check is the previous value 0 instead of 9. Any idea why this is happening? Is there a problem with the binding?
Here is the answer, and include my MVVM Framework's important part. Beside this I put some extra function on it . I can't put here all of my library. But I'm sure it will help.
If you use Commanding you should be aware of CanExecuteChanged in ICommand interface. You should trigger this command when a property changed. (I don't use RelayCommand, it is 3.party .)
Use my DCommand :) this is the most important part
It has easy implementation ,
and has FirePropertyChanged method. This method fires CanExecuteChanged of ICommand if it is not null.
An example Command
Anonymous syntax
DCommand commandPost=new DCommand(()=>{
//TODO:Command's real execute method Post()
},
()=>
{
return this.TextBoxBoundProperty.IsValid;
}
)
Non-anonymous syntax
DCommand commandPost=(Post,Validate);
beside this you should trigger canexecutechanged by following method in your viewModel's ctor.
this.PropertyChanged += (sender, prop) =>
{
//I preffered canExcuteChange for any property changes for my viewmodel class. You could put your own logic. if(prop.PropertyName.equals("thisone"));
//Just works for this class's property changed
this.InvokeOnClassPropertyChange(prop.PropertyName, () =>
{
this.commandPost.FirePropertyChanged();
});
}
InvokeOnClassPropertyChange works when if the property is ViewModel class's property.
public static void InvokeOnClassPropertyChange(this object instance,string PropertyName,Action action)
{
Type type = instance.GetType();
var fulllist = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(w => w.DeclaringType == type).ToList();
if (fulllist.Select(p => p.Name).Contains(PropertyName))
{
action.Invoke();
}
}
The above code shows InvokeOnClassPropertyChange extension method.
The below one shows my DCommand implements ICommand.
public class DCommand :ICommand
{
public void FirePropertyChanged()
{
if (CanExecuteChanged!=null)
CanExecuteChanged(this, EventArgs.Empty);
}
Func<bool> CanExecuteFunc { get; set; }
Action<object> Action { get; set; }
public DCommand(Action<object> executeMethod)
{
this.Action = executeMethod;
}
public DCommand(Action executeMethod)
{
this.Action = new Action<object>(
(prm) =>
{
executeMethod.Invoke();
}
);
}
public DCommand(Action<object> executeMethod, Func<bool> canExecuteMethod)
: this(executeMethod)
{
this.CanExecuteFunc = canExecuteMethod;
}
public DCommand(Action executeMethod, Func<bool> canExecuteMethod)
: this(executeMethod)
{
this.CanExecuteFunc = canExecuteMethod;
}
public bool CanExecute(object parameter=null)
{
if (CanExecuteFunc == null)
return true;
return CanExecuteFunc.Invoke();
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter=null)
{
if (CanExecuteFunc == null || CanExecute(parameter))
{
Action.Invoke(parameter);
}
}
}
After all, If you want to change your ViewModel property when textbox's text changes immediately you should bind by this way.
Text="{Binding BoundProperty,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Below is my code:
<StackPanel>
<TextBox x:Name="TextBox1" Margin="5,0,5,0" Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding OnValidateExecute, Mode=OneWay}" CommandParameter="{Binding Text,ElementName=TextBox1}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button x:Name="btnSave" Width="120" Height="25" Content="click" IsEnabled="{Binding IsOk}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding OnSaveExecute}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
Only add a command parameter.
public class myViewModel : INotifyPropertyChanged
{
public bool IsOk { get; set; }
public string Message { get; set; }
public RelayCommand OnValidateExecute { get; set; }
public myViewModel()
{
OnValidateExecute = new RelayCommand(p =>
{
Message = p as string;
IsValid();
}, p => true);
}
public bool IsValid()
{
bool valid = !string.IsNullOrEmpty(Message);
IsOk = valid;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsOk"));
}
return valid;
}
public event PropertyChangedEventHandler PropertyChanged;
}
It works well.
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With