Catch exceptions with IDataErrorInfo - c#

I want to validate the input of a TextBox. I used the IDataErrorInfo interface to do so. When the user input is bad the property ValueIsValid is set to false. However, I use the property InputValue for the TextInput (its an int). When the user types "1234" the IDataErrorInfo interface checks if the input is correct and sets if needed the ValueIsValid to false.
But when the user types "blabla" the input is not being converted to an int and IDataErrorInfo interface is not being called => ValueIsValid is not set to false.
How do I set ValueIsValid to false when the user types "blabla" in the TextBox?
I can't access the validation.hasError property of the TextBox from my viewmodel because I use MVVM.
ViewModel:
public class ViewModel : IDataErrorInfo
{
public bool ValueIsValid { get; set; }
public string StrErrorMessage
{
get { return "Some Error ..."; }
}
public int InputValue
{
get { return m_inputValue; }
set
{
m_inputValue = value;
NotifyPropertyChanged();
ValueIsValid = true;
}
}
protected int m_inputValue;
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
if (columnName == "InputValue")
{
if (InputValue == 10)
{
ValueIsValid = false;
return "Wrong value in TextBox.";
}
}
return string.Empty;
}
}
}
WPF
<TextBox Text="{Binding InputValue, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding StrErrorMessage}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Related

WPF MVVM view not updated when viewmodels properties are

Context :
I have a treeview with a separate details view injecting by PRISM library when I click on one of my treeviewitem (and I can update all properties of my item with it). All my items have a Enabled property.
Problem :
When I update programmatically my viewmodels property, my object is updated. If I click on an other treeviewitem and come back to the first one, I see the property was updated.
All the updates are good when I enable/disable the item using my details view (the foreground is going grey and the property is change)
But in my case, when I try to update it by a command triggered by a contextMenu it doesn't trigger the view and all the updates... but my viewmodel property is updated...
What am I going wrong ?
I am using ObservableCollection in my treeview, maybe I need to change the type of my collection ?
I have my BaseViewModel who implements NotifyPropertyChanged
public abstract class NotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(Expression<Func<object>> propertyExpression)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(GetPropertyName(propertyExpression)));
}
private string GetPropertyName(Expression<Func<object>> propertyExpression)
{
var unaryExpression = propertyExpression.Body as UnaryExpression;
var memberExpression = unaryExpression == null ? (MemberExpression)propertyExpression.Body : (MemberExpression)unaryExpression.Operand;
var propertyName = memberExpression.Member.Name;
return propertyName;
}
}
So I call the property change method, but Why my view is not updating then ?
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool Enabled
{
get
{
return Model.Enabled;
}
set
{
if (value != Model.Enabled)
{
Model.Enabled = value;
OnPropertyChanged(() => Model.Enabled);
}
}
}
Here is the code of my view (for the command)
<MenuItem Header="Enable/Disable this equipment" Command="{Binding PlacementTarget.Tag.DataContext.ToogleEquipmentCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding}" InputGestureText="CTRL+D"/>
And here is the code of my view (Hierarchical data template from my treeview)
<!-- ModuleItems > IP / Name -->
<HierarchicalDataTemplate DataType="{x:Type siemens:ModuleItemSiemensViewModel}" >
<StackPanel Orientation="Horizontal">
<TextBlock Name="ItemIp"
Text="{Binding Path=Ip}" ContextMenu="{StaticResource ContextMenuEquipment}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding Enabled}" Value="True">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=" / " ContextMenu="{StaticResource ContextMenuEquipment}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter Property="Background" Value="LightGray"/>
</DataTrigger>
<DataTrigger Binding="{Binding Enabled}" Value="True">
<Setter Property="Background" Value="Transparent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Name="ItemName" ContextMenu="{StaticResource ContextMenuEquipment}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
Text="{Binding Path=Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding Enabled}" Value="True">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
EDIT :
Here is the code from my viewmodel and models :
My real problem is when I update an Item (with my property enabled) it update the item, but my list (ModuleItems) is not updated, what I need to do to correctly implement MVVM and to make my fields automatically updated ?
public class ModuleParamSiemensViewModel : ModuleParamBaseViewModel
{
#region Attributes
private ObservableCollection<ModuleItemSiemensViewModel> _moduleItems;
private ModuleParamSiemens _model;
private string _moduleType;
#endregion
#region Constructor
public ModuleParamSiemensViewModel(ModuleParamSiemens moduleParam) : base(moduleParam)
{
this.Model = moduleParam;
this.ModuleType = "Siemens";
ModuleItems = new ObservableCollection<ModuleItemSiemensViewModel>();
Initialize();
}
#endregion
#region Properties
public new ModuleParamSiemens Model
{
get
{
return _model;
}
set
{
if (value != _model)
{
_model = value;
OnPropertyChanged(() => Model);
}
}
}
public new ObservableCollection<ModuleItemSiemensViewModel> ModuleItems
{
get
{
return _moduleItems;
}
set
{
this._moduleItems = value;
OnPropertyChanged(() => ModuleItems);
}
}
public override string ModuleType
{
get
{
return _moduleType;
}
set
{
this._moduleType = value;
OnPropertyChanged(() => ModuleType);
}
}
#endregion
#region Public Methods
public void Initialize()
{
foreach (ModuleItemSiemens item in this.Model.ModuleItems)
{
Add(new ModuleItemSiemensViewModel(item));
}
}
public void Add(ModuleItemSiemensViewModel item)
{
ModuleItems.Add(item);
}
#endregion
}
Model :
public class ModuleParamSiemens : ModuleParam
{
public new ObservableCollection<ModuleItemSiemens> ModuleItems { get; set; }
public ModuleParamSiemens()
{
ModuleItems = new ObservableCollection<ModuleItemSiemens>();
}
}
EDIT 2 :
Add ItemSiemensViewModel
public class ItemSiemensViewModel : ItemBaseViewModel
{
#region Attributes
private ItemSiemens _model;
#endregion
#region Constructor
public ItemSiemensViewModel(ItemSiemens item)
{
this.Model = item;
}
#endregion
#region Properties
public new ItemSiemens Model
{
get
{
return _model;
}
set
{
if (value != _model)
{
_model = value;
OnPropertyChanged(() => Model);
}
}
}
public new OPCInfo Opc
{
get
{
return Model.Opc;
}
set
{
if (value != Model.Opc)
{
Model.Opc = value;
OnPropertyChanged(() => Model.Opc);
}
}
}
public ProtocolInfoSiemens Protocol
{
get
{
return Model.Protocol;
}
set
{
if (value != Model.Protocol)
{
Model.Protocol = value;
OnPropertyChanged(() => Model.Protocol);
}
}
}
#endregion
#region Public Methods
#endregion
}
ItemSiemens :
public class ItemSiemens : Item
{
public ProtocolInfoSiemens Protocol { get; set; }
}
ItemBaseViewModel
public abstract class ItemBaseViewModel : BaseViewModel
{
public OPCInfoBaseViewModel Opc { get; set; }
public ItemBaseViewModel()
{
}
}
Item
public abstract class Item
{
public OPCInfo Opc { get; set; }
}
I have found the answer.
My bindings are correct (or at least it works)
The problem is that I used ObservableCollection collection and when an item is update in this collection it's not even fire an event to say that something has changed (it does for adding and removing items)
So I have implemented my own ItemsChangeObservableCollection (you can look this answer : https://stackoverflow.com/a/33866549/8237280)
And now all my problems in all my app are solved !
You are sending INotifyPropertyChanged on your ModuleItemSiemensViewModel for the property Model.Enabled. This does not make much sense, as nobody is listening on the VM (ModuleItemSiemensViewModel) for this change. The INPC interface does not allow such a kind of update. Every control listens on the same object that it's binding a property. That means you can only send PropertyChanged for properties that are in the same class/instance the interface is declared.
You have to move the NotifyPropertyChanged to the "Model" instance and call it there like this:
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool Enabled
{
get
{
return Model.Enabled;
}
set
{
if (value != Model.Enabled)
{
Model.Enabled = value;
Model.OnPropertyChanged(() => Enabled);
}
}
}

Dynamic Tab Control which can hold User Controls

I want to create a Tab Control which can hold for multiple User Controls.
<TabControl Padding="0">
<TabItem Header="{x:Static p:Resources.Scheduler}"
Visibility="{Binding ShellService.IsSchedulerEnabled,
Converter={StaticResource BoolToVisibilityConverter}}">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Content"
Value="{Binding ShellService.LazySchedulerView.Value}"/>
</Trigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</TabItem>
</TabControl>
The xaml is only for 1 tab item, which control by ShellService.IsSchedulerEnabled and the content is ShellService.LazySchedulerView.Value.
My problem here is that if I want to create a new TabItem, I have to create a new TabItem tag in the xaml.
How can I create a dynamic tab control to hold more than 1 tab item without specifying 'Value' in ContentControl.
public interface IShellService : INotifyPropertyChanged
{
object ShellView { get; }
bool IsSchedulerEnabled { get; set; }
Lazy<object> LazySchedulerView { get; set; }
}
[Export(typeof(IShellService)), Export]
internal class ShellService : Model, IShellService
{
private object _shellView;
private bool _isSchedulerEnabled;
private Lazy<object> _lazySchedulerView;
public object ShellView
{
get { return _shellView; }
set { SetProperty(ref _shellView, value); }
}
public bool IsSchedulerEnabled
{
get { return _isSchedulerEnabled; }
set { SetProperty(ref _isSchedulerEnabled, value); }
}
public Lazy<object> LazySchedulerView
{
get { return _lazySchedulerView; }
set { SetProperty(ref _lazySchedulerView, value); }
}
}
You can use Style for this TabItem. I created some example for you. You should change Bindings to your own. And you should create ObservableCollection of ShellServices and bind it to the TabControl. I hope this helps.
<TabControl ItemsSource="{Binding Objects}">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Header}"></Setter>
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Content" Value="{Binding Text}"/>
</Trigger>
</Style.Triggers>
</Style>
</TabControl.Resources>
</TabControl>
Update
ViewModel Sample
public class OwnObject : ViewModelBase
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged( "Text" ); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void NotifyPropertyChanged( String info )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( info ) );
}
}
}
I bound these objects to TabControl.
private ObservableCollection<OwnObject> _objects = new ObservableCollection<OwnObject>();
public ObservableCollection<OwnObject> Objects
{
get { return _objects; }
set { _objects = value; NotifyPropertyChanged( "Objects" ); }
}

Binding complex property of model to DataGridTextColumn and using Style to set value displayed

Let's say i have the code below...i have a Field class that holds a state and the actual value to display. I have model that defines an instance of this MyField class named Field1. In by DataGrid i am binding to this Field1 and using a style to display the value as well as color the background if IsStale is true. The problem is neither the value, nor the background is being colored. It appears the problem is that when used as is, the datacontext for the Style is MyData object and not in fact the MyField object, even though i specify the binding as "Field1". The error being printed out is "BindingExpression path error: 'IsStale' property not found on 'object' ''MyDataModel'".
How can properly bind to a complex property in a Datagrid's cell such that i can use multiple attributes of the bound model?
class MyField : BaseModel
{
private bool _isStale;
public bool IsStale
{
get { return _isStale; }
set
{
if (_isStale == value) return;
_isStale = value;
NotifyPropertyChanged("IsStale");
}
}
private double _value;
public double Value
{
get { return _value; }
set
{
if (_value.Equals(value)) return;
_value = value;
NotifyPropertyChanged("Value");
}
}
}
class MyDataModel
{
MyField Field1 {get; set;}
public MyData()
{
Field1 = new Field1();
//when ever underlying property of MyField changes, we need to fire property changed because xaml binds to Field1
Field1.PropertyChanged += (o,e) =>
{
MyField field = o as MyField;
if (field!=null)
NotifyPropertyChanged("Field1");
};
}
}
<DataGridTextColumn Header="Weight" Binding="{Binding Field1}" ElementStyle="{StaticResource DGCellStyle}"/>
Style:
<Style x:Key="DGCellStyle" TargetType="TextBlock">
<Setter Property="Width" Value="Auto"/>
<Setter Property="Text" Value="{Binding Value, Converter={StaticResource NumberFormatConverter}, ConverterParameter=0.00}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsStale}" Value="True">
<Setter Property="Background" Value="Pink"/>
</DataTrigger>
</Style.Triggers>
</Style>

Textbox Event Handling in ViewModel

I have a situation, where I am validating a textbox for enabling the button. If the textbox is empty the button should be disabled and vice verse. I can handle the code and achieve the solution, if I write the logic in the code behind of the XAML but I feel thats not the correct way and the event should be handled from the viewModel instead of the code behind.
Here is what I have done:
XAML
<TextBox Grid.Row="1" Margin="6,192,264,0" Height="60" VerticalAlignment="Top"
x:Name="txtDNCNotes" Text="{Binding Path=DNCNotes, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
Visibility="{Binding Path=DNCNoteTxtVisibility}" Grid.Column="1"
behaviour:TextBoxFilters.IsBoundOnChange="True"
TextChanged="TextBox_TextChanged" />
ViewModel
public string DNCNotes
{
get { return _dncNotes; }
set {
if (_dncNotes == value) return;
_dncNotes = value;
OnPropertyChanged("DNCNotes");
}
}
Code behind
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var ctx = LayoutRoot.DataContext as NextLeadWizardViewModel;
BindingExpression binding = txtDNCNotes.GetBindingExpression(TextBox.TextProperty).UpdateSource();
ctx.ShowDoNotContact();
}
I am trying to write following code in the viewModel to achieve the solution but not sure what to write.
public void ShowDoNotContact()
{
Binding myBinding = new Binding("DNCNotes");
//myBinding.Source = DataContext as NextLeadWizardViewModel;
myBinding.Source = txtDNCNotes;
myBinding.Path = new PropertyPath("DNCNotes");
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(txtDNCNotes, TextBox.TextProperty, myBinding);
if (_dncNotes == null)
OkCommand.IsEnabled = false;
else
OkCommand.IsEnabled = CanEnableOk();
}
If you want to validate a TextBox which would disable the button, i would use a command, something similar to this;
private ICommand showDCNoteCommand;
public ICommand ShowDCNoteCommand
{
get
{
if (this.showDCNoteCommand == null)
{
this.showDCNoteCommand = new RelayCommand(this.DCNoteFormExecute, this.DCNoteFormCanExecute);
}
return this.showDCNoteCommand;
}
}
private bool DCNoteFormCanExecute()
{
return !string.IsNullOrEmpty(DCNotes);
}
private void DCNoteFormExecute()
{
DCNoteMethod(); //This a method that changed the text
}
This would ensure that the user is unable to continue, or save to progress as the TextBox should not accept a null or empty value, shown within the DCNoteFormCanExecute() (the DCNotes is property that you have defined within your Viewmodel).
and in the xaml, bind it to the button like so;
<Button Content="Save" Grid.Column="1" Grid.Row="20" x:Name="btnSave" VerticalAlignment="Bottom" Width="75" Command="{Binding ShowDCNoteCommand}"
For validation, you could do something simple like so, using attribute validation, using this reference using System.ComponentModel.DataAnnotations;
[Required(ErrorMessage = "DCNotes is required")]
[RegularExpression(#"^[a-zA-Z''-'\s]{1,5}$", ErrorMessage = "DCNotes must contain no more then 5 characters")] //You can change the length of the property to meet the DCNotes needs
public string DCNotes
{
get { return _DCNotes; }
set
{
if (_DCNotes == value)
return;
_DCNotes = value;
OnPropertyChanged("DCNotes");
}
}
and within the xaml, you could create a Resource to highlight the box to notify the user of the textbox not been filled out;
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin"
Value="4" />
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin"
Value="4" />
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
I hope this helps, otherwise, here's the link that might help;
http://www.codeproject.com/Articles/97564/Attributes-based-Validation-in-a-WPF-MVVM-Applicat
OR
http://www.codearsenal.net/2012/06/wpf-textbox-validation-idataerrorinfo.html#.UOv01G_Za0t
The ViewModel is an acceptable place to add supporting properties for your View that do not effect your model. For example, something along the lines of:
public bool DncCanExecute
{
get
{
return "" != _dncNotes;
}
}
public string DNCNotes
{
get { return _dncNotes; }
set {
if (_dncNotes == value) return;
if (("" == _dncNotes && "" != value) || ("" != _dncNotes && "" == value))
{
_dncNotes = value;
OnPropertyChanged("DncCanExecute");
}
else
{
_dncNotes = value;
}
OnPropertyChanged("DNCNotes");
}
}
From there, you can just bind the Button.IsEnabled property to the DncCanExecute property to get the desired functionality.

How to show a tooltip on invalid input into a textbox

I am trying to setup validation for a phone number field in a WPF application using MVVM. I have the textbox text bound but cant seem to figure out the logic involved in rejecting input and popping up a tooltip. Any suggestions would be appreciated.
[Required]
public string PhoneNumber
{
get
{
return EntityPhone.PhoneNumber;
}
set
{
int intValue = 0;
if(!int.TryParse(value, out intValue))
{
// ToolTip tt = new ToolTip();
// tt.Content = "Invalid Character. Please enter a valid 10-digit number";
}
EntityPhone.PhoneNumber = value;
NotifyOfPropertyChange(() => PhoneNumber);
}
}
First you'll want to make your class inherit IDataErrorInfo, which is used by WPF for validation purposes.
public class MyClass : IDataErrorInfo
{
...
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get { return null; }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (columnName == "PhoneNumber")
{
// Validate property and return a string if there is an error
return "Some error";
}
// If there's no error, null gets returned
return null;
}
}
#endregion
}
Next, tell your binding that it should be validating the value when it changes
<TextBox Text="{Binding Path=PhoneNumber, ValidatesOnDataErrors=True}" ... />
And finally, create a validation template. Here's the style/template I usually use
<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding
Path=(Validation.Errors)[0].ErrorContent,
RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>
</Style.Triggers>
</Style>

Categories

Resources