I working on WPF application using MVVM pattern.
the question is :
How to Check wether the Textboxes content are valid or not and by the way make a Save Button Disable until all textboxes fill by valid data.
I googled more and more but in all scenario the answer was implementing IDataerrorInfo on the model but i using entity frameworks entity as model so can not do the implementation.
please give me some advice or links .
thanks in advance.
Below my project description:
the XAML:
<TextBox Name="txtName" Height="23" HorizontalAlignment="Left" Margin="56,42,0,0" VerticalAlignment="Top" Width="120" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<Binding Path="CurrentItem.Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<vm:NotNullValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
and the PersonViweModel.cs
public class PersonViewModel : WorkspaceViewModel
{
SDPS.Business.Base.BaseBusiness<Person> bz = new Business.Base.BaseBusiness<Person>();
#region Constructor
public PersonViewModel(string displayTitle)
{
Items = bz.GetAll();
DisplayName = displayTitle;
NewCommand = new RelayCommand(p => NewItem());
SaveCommand = new RelayCommand(p => SaveItem());
UpdateCommand = new RelayCommand(p => UpdateItem(), p => CanUpdateOrDelete);
DeleteCommand = new RelayCommand(p => DeleteItem(), p => CanUpdateOrDelete);
}
#endregion
#region Methods
private void NewItem()
{
CurrentItem = new Person();
}
private void SaveItem()
{
bz.Add(CurrentItem);
Items.Add(CurrentItem);
}
private void DeleteItem()
{
bz.Delete(CurrentItem);
Items.Remove(CurrentItem);
OnPropertyChanged("Items");
OnPropertyChanged("CurrentItem");
}
public void UpdateItem()
{
bz.Update(CurrentItem);
OnPropertyChanged("Items");
}
#endregion
#region Properties
public Person CurrentItem
{
get { return _CurrentItem; }
set
{
_CurrentItem = value;
OnPropertyChanged("CurrentItem");
}
}
public ObservableCollection<Person> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged("Items");
}
}
public bool CanUpdateOrDelete { get { return CurrentItem != null; } }
#endregion
#region Variables
private ObservableCollection<Person> _items;
private Person _CurrentItem;
#endregion
}
and the ValidationRule Class as below:
public class NotNullValidationRule : ValidationRule
{
public virtual bool HasError
{
get
{
return null;
}
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var result = new ValidationResult(true, null);
if(value!=null)
if (string.IsNullOrWhiteSpace( value.ToString()) )
{
result = new ValidationResult(false, "null string not allowed");
}
return result;
}
}
Related
I created a WPF application where I create a list of items to be executed, as a Treeview. On a click event, I parse the ObservableCollection items one by one. This observableCollection is set as the DataContext for the treeview. When running the tests, I want to highlight the current running item in the Treeview.
I have the implemented following code, but the highlighting on the Treeview (visuallY) does not seem to happen. I checked that the "IsSelected" property does get set/unset as programmed.
I am not sure were I went wrong. Could you point out where the mistake is.
I have this class used as a DataContext to the TreeView (named mainTree).
class mytreefile : INotifyPropertyChanged
{
private string _name { get; set; }
public ObservableCollection <mytreefile> children { get; set; }
bool? _isSelected = false;
public bool? IsSelected
{
get { return _isSelected; }
set { SetIsSelected(value); }
}
void SetIsSelected(bool? val)
{
_isSelected = val;
}
public mytreefile(string value)
{
_name = value;
children = new ObservableCollection<mytreefile>();
}
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The XAML file is
<Grid.Resources>
<ResourceDictionary>
<HierarchicalDataTemplate x:Key="tvTemplate" ItemsSource="{Binding children, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<ContentPresenter Content="{Binding _name, Mode=TwoWay}" Margin="2,0" />
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Grid.Resources>
<TreeView x:Name="mainTree" Grid.Row="0" Grid.Column="0" Grid.RowSpan="4" Background="WhiteSmoke"
Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="1,0,2,0" SelectedItemChanged="mainTree_SelectedItemChanged"
ItemTemplate="{StaticResource tvTemplate}"
ItemsSource="{Binding}" DataContext="{Binding nodes}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="FontWeight" Value="Normal" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
And my MainWindow code is:
public partial class MainWindow : Window
{
ObservableCollection<mytreefile> nodes = new ObservableCollection<mytreefile>();
mytreefile mtf = null;
Thread thThread = null;
int gnCount = 0;
private void LoadTree ()
{
mytreefile tf1 = new mytreefile("Group1");
nodes.Add(tf1);
mytreefile subtf1 = new mytreefile("Sub Item 1");
mytreefile subtf2 = new mytreefile("Sub Item 2");
mytreefile subtf3 = new mytreefile("Sub Item 3");
mytreefile subtf4 = new mytreefile("Sub Item 4");
tf1.children.Add(subtf1); tf1.children.Add(subtf2); tf1.children.Add(subtf3); tf1.children.Add(subtf4);
maintree.DataContext = nodes;
}
private void OnButton1_click()
{
mtf = nodes.ElementAt(0);
gnCount = 0;
thThread = new Thread(new ThreadStart(this.myThread));
thThread.Start();
}
public void myThread ()
{
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send,
new Action(() => SetTreeItem(i)));
}
}
public void SetTreeItem(int i)
{
if (gnCount > 0) {
mytreefile mtreeitem = mtf.children.ElementAt(gnCount-1);
mtreeitem.IsSelected = false;
}
mytreefile mtreeitem = mtf.children.ElementAt(gnCount++);
mtreeitem.IsSelected = true;
}
}
The problem was with the "mytreefile" class.
The below class works fine. The way the "IsSelected" implementation was done made the difference. Posting the code for reference.
class mytreefile : INotifyPropertyChanged
{
private string _name { get; set; }
public ObservableCollection <mytreefile> children { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != this._isSelected)
{
this._isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
public mytreefile(string value)
{
_name = value;
children = new ObservableCollection<mytreefile>();
}
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I would like to have a converter system for my Xamarin and WPF project. I don't want to save any units in the database, so I want directly convert the textbox-values when user change the unit.
I made public a few Observable Collections like;
public class AreaList : ObservableCollection<Unit>
{
public AreaList() : base()
{
Add(new Unit("mm²"));
Add(new Unit("cm²"));
Add(new Unit("dm²"));
Add(new Unit("m²"));
}
}
public class Unit
{
private string name;
public Unit(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
set { name = value; }
}
}
In the View i bind the collection to my combo box. I gave my TextBox the name of his binding property(Text="{Binding TxtBoxValue}" => x:Name="TxtBoxValue"). The ConvertUnitValueCommand set this name as a string in the view model to know which variable the converter function should use when the unit is changed.
View
<UserControl.Resources>
<c:AreaList x:Key="AreaListData" />
</UserControl.Resources>
<TextBox x:Name="TxtBoxValue"
Text="{Binding Mode=TwoWay, Path=TxtBoxValue, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<ComboBox IsSynchronizedWithCurrentItem="True"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding Unit,Mode=OneWayToSource}"
ItemsSource="{Binding Source={StaticResource AreaListData}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ConvertUnitValueCommand}"
CommandParameter="{Binding ElementName=TxtBoxValue, Path=Name}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
ViewModel
private string ConvertControlName;
private void ConvertUnitValue(object obj)
{
ConvertControlName = obj.ToString();
}
public Unit Unit
{
get => Get<Unit>();
set
{
if (ConvertControlName != null)
{
FieldInfo variable = this.GetType().GetField(ConvertControlName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic);
//Get the Value from setted Binding Variable
double oldValue = (double)variable.GetValue(this);
//Convert the value
if (oldValue > 0)
{
double newValue = Converts.ConvertUnitValue(Unit, value, oldValue);
variable.SetValue(this, newValue);
}
Set(value);
}
}
Maybe anyone can give me some inspiration to do it better.
The following example normalizes the user input to the base unit m²:
Unit.cs
public class Unit
{
public Unit(string name, decimal baseFactor)
{
this.Name = name;
this.BaseFactor = baseFactor;
}
#region Overrides of Object
/// <inheritdoc />
public override string ToString() => this.Name;
#endregion
public string Name { get; set; }
public decimal BaseFactor { get; set; }
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Units = new List<Unit>()
{
new Unit("mm²", (decimal) (1 / Math.Pow(1000, 2))),
new Unit("cm²", (decimal) (1 / Math.Pow(100, 2))),
new Unit("dm²", (decimal) (1 / Math.Pow(10, 2))),
new Unit("m²", 1)
};
}
private void NormalizeValue()
{
this.NormalizedValue = this.UnitValue * this.SelectedUnit.BaseFactor;
}
private List<Unit> units;
public List<Unit> Units
{
get => this.units;
set
{
this.units = value;
OnPropertyChanged();
}
}
private Unit selectedUnit;
public Unit SelectedUnit
{
get => this.selectedUnit;
set
{
this.selectedUnit = value;
OnPropertyChanged();
NormalizeValue();
}
}
private decimal unitValue;
public decimal UnitValue
{
get => this.unitValue;
set
{
this.unitValue = value;
OnPropertyChanged();
NormalizeValue();
}
}
private decimal normalizedValue;
public decimal NormalizedValue
{
get => this.normalizedValue;
set
{
this.normalizedValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ManiWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DatContext>
<StackPanel>
<!-- Input -->
<TextBox Text="{Binding UnitValue}" />
<ComboBox ItemsSource="{Binding Units}"
SelectedItem="{Binding SelectedUnit}" />
<TextBlock Text="{Binding NormalizedValue}" />
</StackPanel>
</Window>
Reusable solution
A reusable solution would be to create a custom control, which derives from TextBox and encapsulates the normalization logic and the control design.
The following custom control NormalizingNumericTextBox extends TextBox and converts two way from non-normalized value to normalized and back.
It is basically a TextBox aligned with a ComboBox as Unit selector.
It may not be perfect, but it is ready to use and it just took me about 10 minutes to merge the previous answer into this custom control.
NormalizingNumericTextBox supports any type of unit describing a numeric value.
Just bind the NormalizingNumericTextBox.Units property to collection of any kind of Unit implementation e.g. weight, length, currency, etc.
Bind to NormalizingNumericTextBox.NormalizedValue to get/set the normalized value. Setting this property will convert the value to the current NormalizingNumericTextBox.SelectedUnit.
Bind to NormalizingNumericTextBox.Text for the raw input value.
Ensure that the default Style (see below) is added to the ResourceDictionary inside /Themes/Generic.xaml. Customize this Style to customize appearance.
ManiWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DatContext>
<StackPanel>
<!-- Input -->
<NormalizingUnitTextBox NormalizedValue="{Binding NormalizedValue}"
Units="{Binding Units}"
Width="180" />
<!--
Test to show/manipulate current normalized value of the view model.
An entered normalized value will be converted back to the current NormalizingNumericTextBox.Unit -->
<TextBox Background="Red" Text="{Binding NormalizedUnitValue}"/>
</StackPanel>
</Window>
Unit.cs
public class Unit
{
public Unit(string name, decimal baseFactor)
{
this.Name = name;
this.BaseFactor = baseFactor;
}
#region Overrides of Object
/// <inheritdoc />
public override string ToString() => this.Name;
#endregion
public string Name { get; set; }
public decimal BaseFactor { get; set; }
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Units = new List<Unit>()
{
new Unit("m²", 1),
new Unit("dm²", (decimal) (1/Math.Pow(10, 2))),
new Unit("cm²", (decimal) (1/Math.Pow(100, 2))),
new Unit("mm²", (decimal) (1/Math.Pow(1000, 2)))
};
}
public List<Unit> Units { get; set; }
private decimal normalizedValue;
public decimal NormalizedValue
{
get => this.normalizedValue;
set
{
this.normalizedValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
NormalizingNumericTextBox.cs
[TemplatePart(Name = "PART_UnitsItemsHost", Type = typeof(ItemsControl))]
public class NormalizingNumericTextBox : TextBox
{
public static readonly DependencyProperty UnitsProperty = DependencyProperty.Register(
"Units",
typeof(IEnumerable<Unit>),
typeof(NormalizingNumericTextBox),
new PropertyMetadata(default(IEnumerable<Unit>), NormalizingNumericTextBox.OnUnitsChanged));
public IEnumerable<Unit> Units
{
get => (IEnumerable<Unit>) GetValue(NormalizingNumericTextBox.UnitsProperty);
set => SetValue(NormalizingNumericTextBox.UnitsProperty, value);
}
public static readonly DependencyProperty SelectedUnitProperty = DependencyProperty.Register(
"SelectedUnit",
typeof(Unit),
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(
default(Unit),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
NormalizingNumericTextBox.OnSelectedUnitChanged));
public Unit SelectedUnit
{
get => (Unit) GetValue(NormalizingNumericTextBox.SelectedUnitProperty);
set => SetValue(NormalizingNumericTextBox.SelectedUnitProperty, value);
}
public static readonly DependencyProperty NormalizedValueProperty = DependencyProperty.Register(
"NormalizedValue",
typeof(decimal),
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(
default(decimal),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
NormalizingNumericTextBox.OnNormalizedValueChanged));
public decimal NormalizedValue
{
get => (decimal) GetValue(NormalizingNumericTextBox.NormalizedValueProperty);
set => SetValue(NormalizingNumericTextBox.NormalizedValueProperty, value);
}
private ItemsControl PART_UnitsItemsHost { get; set; }
private bool IsNormalizing { get; set; }
static NormalizingNumericTextBox()
{
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(typeof(NormalizingNumericTextBox)));
}
public NormalizingNumericTextBox()
{
}
private static void OnNormalizedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _this = d as NormalizingNumericTextBox;
_this.ConvertNormalizedValueToNumericText();
}
private static void OnSelectedUnitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as NormalizingNumericTextBox).NormalizeNumericText();
}
private static void OnUnitsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _this = d as NormalizingNumericTextBox;
_this.SelectedUnit = _this.Units.FirstOrDefault();
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_UnitsItemsHost = GetTemplateChild("PART_UnitsItemsHost") as ItemsControl;
if (this.PART_UnitsItemsHost == null)
{
throw new InvalidOperationException($"{nameof(this.PART_UnitsItemsHost)} not found in ControlTemplate");
}
this.PART_UnitsItemsHost.SetBinding(
Selector.SelectedItemProperty,
new Binding(nameof(this.SelectedUnit)) {Source = this});
this.PART_UnitsItemsHost.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding(nameof(this.Units)) {Source = this});
this.SelectedUnit = this.Units.FirstOrDefault();
}
#region Overrides of TextBoxBase
/// <inheritdoc />
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (this.IsNormalizing)
{
return;
}
NormalizeNumericText();
}
/// <inheritdoc />
protected override void OnTextInput(TextCompositionEventArgs e)
{
// Suppress non numeric characters
if (!decimal.TryParse(e.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal _))
{
e.Handled = true;
return;
}
base.OnTextInput(e);
}
#endregion Overrides of TextBoxBase
private void NormalizeNumericText()
{
this.IsNormalizing = true;
if (decimal.TryParse(this.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal numericValue))
{
this.NormalizedValue = numericValue * this.SelectedUnit.BaseFactor;
}
this.IsNormalizing = false;
}
private void ConvertNormalizedValueToNumericText()
{
this.IsNormalizing = true;
decimal value = this.NormalizedValue / this.SelectedUnit.BaseFactor;
this.Text = value.ToString(CultureInfo.CurrentCulture);
this.IsNormalizing = false;
}
}
Generic.xaml
<ResourceDictionary>
<Style TargetType="NormalizingNumericTextBox">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="DarkGray" />
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NormalizingNumericTextBox">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" Grid.Column="0" Margin="0" />
<ComboBox x:Name="PART_UnitsItemsHost" Grid.Column="1" BorderThickness="0" HorizontalAlignment="Right" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
I have not much idea about your code impact but I would suggest you try below design which uses MVVM Pattern which removes tight coupling between UI and Backend.
I have separate out the things here
your XAML will have code like
<TextBox x:Name="unitTextbox"
Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<ComboBox IsSynchronizedWithCurrentItem="True"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedUnit}"
ItemsSource="{Binding AvailableUnits}">
</ComboBox>
Your ViewModel will be like
public class MainVm : Observable
{
#region Private Fields
private double _value;
private ObservableCollection<Unit> _availableUnits;
private Unit _selectedUnit;
private Unit _previouslySelected;
#endregion Private Fields
#region Public Constructors
public MainVm()
{
_availableUnits = new ObservableCollection<Unit>()
{
new Unit("mm²"),
new Unit("cm²"),
new Unit("dm²"),
new Unit("m²")
};
}
#endregion Public Constructors
#region Public Properties
public double Value
{
get
{
return _value;
}
set
{
if (_value != value)
{
_value = value;
OnPropertyChanged();
}
}
}
public Unit SelectedUnit
{
get { return _selectedUnit; }
set
{
_previouslySelected = _selectedUnit;
_selectedUnit = value;
// call to value conversion function
// convert cm² to mm² or anything
Value = UnitConvertor.Convert(_value, _previouslySelected.Name, _selectedUnit.Name);
OnPropertyChanged();
}
}
public ObservableCollection<Unit> AvailableUnits => _availableUnits;
#endregion Public Properties
}
My Observable class will be like
public class Observable : INotifyPropertyChanged
{
#region Public Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion Public Events
#region Protected Methods
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion Protected Methods
}
better to use an enum for units
This is my XAML file:
<Window x:Class="WpfListView.MainWindow"
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:local="clr-namespace:WpfListView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView Margin="10" Name="lvUsers">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Margin="0,0,5,0">
<Binding Path="Mail" Mode="TwoWay">
<Binding.ValidationRules>
<local:NameValidation>
<local:NameValidation.Params>
<local:NameValidationParameters
OriginalTree="{Binding Source={x:Reference lvUsers}}"
OriginalName="{Binding RelativeSource={RelativeSource Self}}"/> <!-- I want the OriginalName to be TextBox.Text-->
</local:NameValidation.Params>
</local:NameValidation>
</Binding.ValidationRules>
</Binding>
</TextBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
This is my MainWindow class:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Mail = "john#doe-family.com" });
items.Add(new User() { Name = "Jane Doe", Age = 39, Mail = "jane#doe-family.com" });
items.Add(new User() { Name = "Sammy Doe", Age = 7, Mail = "sammy.doe#gmail.com" });
lvUsers.ItemsSource = items;
}
}
And this is my ValidationRule class:
public class NameValidationParameters : DependencyObject
{
public ListView OriginalTree
{
get { return (ListView)this.GetValue(OriginalTreeProperty); }
set { this.SetValue(OriginalTreeProperty, value); }
}
public string OriginalName
{
get { return (string)this.GetValue(OriginalNameProperty); }
set { this.SetValue(OriginalNameProperty, value); }
}
public static readonly DependencyProperty OriginalTreeProperty
= DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
typeof(NameValidationParameters));
public static readonly DependencyProperty OriginalNameProperty
= DependencyProperty.Register(nameof(OriginalName), typeof(string),
typeof(NameValidationParameters));
}
public class NameValidation : ValidationRule
{
public string ErrorMessage
{ get; set; }
public NameValidationParameters Params { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
ValidationResult lResult = null;
return lResult;
}
}
As you can see, when the NameValidation.Validate is called, I want NameValidation.Params to be populated with correct variables:
NameValidation.Params.OriginalTree should be the original ListView. This I can get.
NameValidation.Params.OriginalName should be the current TextBox's Text. In this case, it is bound to Mail, so I would expect to see that it is the email address like john#doe-family.com. However I can't get this out; all I can see is that NameValidation.Params.OriginalName is "WpfListView.NameValidationParameters"
Also, I want to be able to access the current index in the list inside the NameValidation.Validate method. How can I get it?
As mentioned elsewhere,
Validation rules do not hold or inherit a DataContext this will prevent binding from working as expected if you try to declare everything inline. Instead, declare your bindable rule options as a resource and set the property on your custom rule using a StaticBinding.
I've modified your TextBox to add a resource parameter:
<TextBox Grid.Column="0" Margin="0,0,5,0" x:Name="box1">
<TextBox.Resources>
<local:NameValidationParameters OriginalName="{Binding Mail}" OriginalTree="{Binding Source={x:Reference lvUsers}}" x:Key="Parameters"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="Mail" Mode="TwoWay">
<Binding.ValidationRules>
<local:NameValidation Params="{StaticResource Parameters}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And NameValidationParameters to implement Freezable
public class NameValidationParameters : Freezable
{
public ListView OriginalTree
{
get { return (ListView)this.GetValue(OriginalTreeProperty); }
set { this.SetValue(OriginalTreeProperty, value); }
}
public string OriginalName
{
get { return (string)this.GetValue(OriginalNameProperty); }
set { this.SetValue(OriginalNameProperty, value); }
}
public static readonly DependencyProperty OriginalTreeProperty
= DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
typeof(NameValidationParameters));
public static readonly DependencyProperty OriginalNameProperty
= DependencyProperty.Register(nameof(OriginalName), typeof(object),
typeof(NameValidationParameters));
protected override Freezable CreateInstanceCore()
{
return new NameValidationParameters();
}
}
However, Here is an example of how i implemented INotifyDataErrorInfo in my project. This is a very clean way to trigger validation errors and xaml allows you to configure the display of errors
Model class:
public class AttributionInput
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
public DateTime? EndDate { get; set; }
}
ModelWrapper Generic:
public class ModelWrapper<T> : NotifyDataErrorInfoBase
{
public T Model { get; protected set; }
public ModelWrapper(T model)
{
Model = model;
}
public ModelWrapper()
{
}
protected virtual TValue GetValue<TValue>([CallerMemberName] string propertyName = null)
{
return (TValue)typeof(T).GetProperty(propertyName)?.GetValue(Model);
}
protected virtual void SetValue<TValue>(TValue value, [CallerMemberName] string propertyName = null)
{
typeof(T).GetProperty(propertyName)?.SetValue(Model, value);
OnPropertyChanged(propertyName);
ValidatePropertyInternal(propertyName, value);
}
private void ValidatePropertyInternal(string propertyName, object currentValue)
{
ClearErrors(propertyName);
ValidateDataAnnotations(propertyName, currentValue);
ValidateCustomErrors(propertyName);
}
protected virtual IEnumerable<string> ValidateProperty(string propertyName)
{
return null;
}
private void ValidateCustomErrors(string propertyName)
{
var errors = ValidateProperty(propertyName);
if (errors != null)
{
foreach (var error in errors)
{
AddError(propertyName, error);
}
}
}
private void ValidateDataAnnotations(string propertyName, object currentValue)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(Model) { MemberName = propertyName };
Validator.TryValidateProperty(currentValue, context, results);
foreach (var result in results)
{
AddError(propertyName, result.ErrorMessage);
}
}
}
Generic Implementation:
public class AttributionInputWrapper : ModelWrapper<AttributionInput>
{
public AttributionInputWrapper(AttributionInput model) : base(model)
{
}
public DateTime? StartDate
{
get => GetValue<DateTime?>();
set
{
SetValue(value);
if (EndDate < StartDate) EndDate = StartDate;
}
}
public DateTime? EndDate
{
get => GetValue<DateTime?>();
set
{
SetValue(value);
if (EndDate < StartDate) StartDate = EndDate;
}
}
protected override IEnumerable<string> ValidateProperty(string propertyName)
{
if (propertyName == nameof(EndDate) || propertyName == nameof(StartDate))
{
//if (StartDate.Value.Date > EndDate.Value.Date) yield return "Start Date must be <= End Date";
if (EndDate != null && (EndDate.Value.DayOfWeek == DayOfWeek.Saturday || EndDate.Value.DayOfWeek == DayOfWeek.Sunday))
yield return "Please select a week day";
if (StartDate != null && (StartDate.Value.DayOfWeek == DayOfWeek.Saturday || StartDate.Value.DayOfWeek == DayOfWeek.Sunday))
yield return "Please select a week day";
}
}
}
ViewModel:
public class QueryViewModel : DetailViewModelBase, ICommonViewModel
{
private AttributionInputWrapper _attributionInput;
public AttributionInputWrapper AttributionInput
{
get => _attributionInput;
set
{
_attributionInput = value;
OnPropertyChanged();
}
}
}
View :
<Style TargetType="DatePicker">
<Setter Property="Margin" Value="{StaticResource MarginString}"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="Placeholder1"/>
<TextBlock Text="{Binding ElementName=Placeholder1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" FontSize="9"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<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>
<DatePicker x:Name="StartDateBox" SelectedDate="{Binding AttributionInput.StartDate, UpdateSourceTrigger=PropertyChanged}" DisplayDateStart="10/01/2017" DisplayDateEnd="{x:Static system:DateTime.Now}"/>
I am trying to create a File Explorer style TreeView/ListView window that will allow the user to select a "Project" from within a "ProjectFolder".
The "ProjectFolder" tree is bound to a property on my ViewModel called "RootProjectFolders" and the "Project" list is bound to a property called "ProjectsInSelectedFolder". Things were mostly working; however, I was getting null exceptions when I first loaded the window because the "SelectedFolder" had not yet been set. When I tried to implement a simple check to make sure that the "SelectedFolder" was not null, my "Project" ListView stopped refreshing.
if ((this.SelectedFolder != null) && (this.SelectedFolder.ProjectFolder.Projects != null))
{
foreach (Project project in this.SelectedFolder.ProjectFolder.Projects)
{
_projectsInSelectedFolder.Add(new ProjectViewModel(project));
}
}
base.RaisePropertyChangedEvent("ProjectsInSelectedFolder");
If I remove (this.SelectedFolder != null) from the above, the ListView will update, but I will get an NullException error. Why is that check breaking my binding?
Following up on the request for additional information, here is the XAML of the TreeView and ListView that are binding to the properties on the ViewModel:
<TreeView Name="treeviewProjectFolders" Grid.Column="0"
ItemsSource="{Binding Path=RootProjectFolders}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<GridSplitter Name="splitterProjects" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<ListView Name="listviewProjects" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Path=ProjectsInSelectedFolder}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the ViewModel
public class SelectProjectViewModel : ViewModelBase
{
#region Fields
List<ProjectViewModel> _projectsInSelectedFolder;
List<ProjectFolderViewModel> _rootProjectFolders;
static ProjectFolderViewModel _selectedFolder = null;
ProjectViewModel _selectedProject;
#endregion // Fields
#region Constructor
public SelectProjectViewModel(ProjectFolders rootProjectFolders)
{
if (_rootProjectFolders != null) { _rootProjectFolders.Clear(); }
_rootProjectFolders = new List<ProjectFolderViewModel>();
foreach (ProjectFolder rootFolder in rootProjectFolders)
{
_rootProjectFolders.Add(new ProjectFolderViewModel(rootFolder, this));
}
_projectsInSelectedFolder = new List<ProjectViewModel>();
// Subscribe to events
this.PropertyChanged += OnPropertyChanged;
}
#endregion // Constructor
#region Properties
public List<ProjectFolderViewModel> RootProjectFolders
{
get
{
return _rootProjectFolders;
}
}
public List<ProjectViewModel> ProjectsInSelectedFolder
{
get
{
return _projectsInSelectedFolder;
}
}
public ProjectFolderViewModel SelectedFolder
{
get
{
return _selectedFolder;
}
set
{
if (_selectedFolder != value)
{
_selectedFolder = value;
}
}
}
public ProjectViewModel SelectedProject
{
get
{
return _selectedProject;
}
set
{
_selectedProject = value;
base.RaisePropertyChangedEvent("SelectedProject");
}
}
#endregion // Properties
#region Methods
public void FindSelectedFolder(ProjectFolderViewModel root)
{
if (root.IsSelected) { _selectedFolder = root; }
else
{
foreach (ProjectFolderViewModel folder in root.Children)
{
if (_selectedFolder == null)
{
FindSelectedFolder(folder);
}
}
}
}
#endregion // Methods
#region Event Handlers
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SelectedFolder":
_selectedFolder = null;
foreach (ProjectFolderViewModel root in this.RootProjectFolders)
{
if (_selectedFolder == null)
{
this.FindSelectedFolder(root);
}
}
_projectsInSelectedFolder.Clear();
if ((this.SelectedFolder != null) && (this.SelectedFolder.ProjectFolder.Projects != null))
{
foreach (Project project in this.SelectedFolder.ProjectFolder.Projects)
{
_projectsInSelectedFolder.Add(new ProjectViewModel(project));
}
}
base.RaisePropertyChangedEvent("ProjectsInSelectedFolder");
break;
}
}
#endregion // Event Handlers
Also, here is the ViewModel for the individual project folders that are used to raise the "SelectedFolder" property:
public class ProjectFolderViewModel : ViewModelBase
{
#region Fields
ReadOnlyCollection<ProjectFolderViewModel> _children;
List<ProjectFolderViewModel> _childrenList;
bool _isExpanded;
bool _isSelected;
ProjectFolderViewModel _parentNode;
SelectProjectViewModel _parentTree;
ProjectFolder _projectFolder;
#endregion // Fields
#region Constructor
public ProjectFolderViewModel(ProjectFolder projectFolder, SelectProjectViewModel parentTree) : this(projectFolder, parentTree, null)
{ }
private ProjectFolderViewModel(ProjectFolder projectFolder, SelectProjectViewModel parentTree, ProjectFolderViewModel parentNode)
{
_projectFolder = projectFolder;
_parentTree = parentTree;
_parentNode = parentNode;
_childrenList = new List<ProjectFolderViewModel>();
foreach (ProjectFolder child in _projectFolder.ChildFolders)
{
_childrenList.Add(new ProjectFolderViewModel(child, _parentTree));
}
_children = new ReadOnlyCollection<ProjectFolderViewModel>(_childrenList);
}
#endregion // Constructor
#region Properties
public ReadOnlyCollection<ProjectFolderViewModel> Children
{
get
{
return _children;
}
}
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parentNode != null)
_parentNode.IsExpanded = true;
}
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
base.RaisePropertyChangedEvent("IsSelected");
//if (_isSelected)
//{
_parentTree.RaisePropertyChangedEvent("SelectedFolder");
//}
}
}
public string Name
{
get
{
return _projectFolder.Name;
}
}
public ProjectFolder ProjectFolder
{
get
{
return _projectFolder;
}
}
#endregion // Properties
Change all your
List<T> to observablecollection<T>
because when ever there is new file or folder your adding the Item, your not creating new List, since observablecollection implements INotifyCollectionChanged, and INotifyPropertyChanged it'll internally take care of notifying and refreshing the View. But list cant do that
This might be simple for you guys but am just starting in WPF, and always my mind thinks in terms of Winforms, and am wrong all the time.
Anyway here is my situation. I have label in my view like below:
UserControl
<UserControl.Resources>
<Converters:BooleanToVisibilityConverter x:Key="visibilityConverter"></Converters:BooleanToVisibilityConverter>
<!-- Error Handling -->
<Converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<Converters:ErrorConverter x:Key="errorConverter"/>
<ControlTemplate x:Key="ErrorTemplate">
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors), Converter={StaticResource errorConverter}}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="comboBoxInError" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors), Converter={StaticResource errorConverter}}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
Label
<Label Name="IsImageValid" Content="Image Created" Margin="0,7,-1,0" Style="{StaticResource LabelField}"
Grid.ColumnSpan="2" Grid.Row="15" Width="90" Height="28" Grid.RowSpan="2"
Grid.Column="1" IsEnabled="True"
Visibility="{Binding IsImageValid,Converter={StaticResource BooleanToVisibilityConverter}}" />
I am trying to call the this label in my view model but not sure how.
I have t following method in the viewmodel, planning to use the label to display some message based on the condition like below.
ViewModel
public class MetadataViewModel : NotificationObject, IMetadataViewModel
{
#region :: Properties ::
private IEventAggregator eventAggregator;
private IImageResizerService imageResizer;
private string headerInfo;
public string HeaderInfo
{
get
{
return headerInfo;
}
set
{
if (this.headerInfo != value)
{
this.headerInfo = value;
this.RaisePropertyChanged(() => this.HeaderInfo);
}
}
}
public ICommand SaveCommand
{
get;
private set;
}
public ICommand CloseCommand
{
get;
private set;
}
public ICommand DeleteCommand
{
get;
private set;
}
public ICommand SubmitCommand
{
get;
private set;
}
public ICommand UnSubmitCommand
{
get;
private set;
}
public ICommand LocationSearchCommand
{
get;
private set;
}
public ICommand SubjectSearchCommand
{
get;
private set;
}
public ICommand RemoveLocationCommand
{
get;
private set;
}
public ICommand RemoveSubjectCommand
{
get;
private set;
}
private StoryItem selectedStory;
public StoryItem SelectedStory
{
get
{
return this.selectedStory;
}
set
{
if (this.selectedStory != value)
{
this.selectedStory = value;
this.RaisePropertyChanged(() => this.SelectedStory);
// raise dependencies
this.RaisePropertyChanged(() => this.CanSave);
this.RaisePropertyChanged(() => this.CanUnSubmit);
this.RaisePropertyChanged(() => this.CanDelete);
}
}
}
public List<Program> ProgramList
{
get;
private set;
}
public List<Genre> GenreList
{
get;
private set;
}
public List<Copyright> CopyrightList
{
get;
private set;
}
public bool CanSave
{
get
{
bool canSave = false;
if (this.SelectedStory.IsLockAvailable)
{
if (!this.SelectedStory.Submitted)
{
canSave = true;
}
}
return canSave;
}
}
public bool CanDelete
{
get
{
bool canDelete = false;
if (this.SelectedStory.IsLockAvailable)
{
if (!this.SelectedStory.Submitted)
{
canDelete = true;
}
}
return canDelete;
}
}
public bool CanUnSubmit
{
get
{
bool canUnSubmit = false;
if (this.SelectedStory.IsLockAvailable)
{
if (this.SelectedStory.Submitted)
{
canUnSubmit = true;
}
}
return canUnSubmit;
}
}
#endregion
#region :: Contructor ::
[ImportingConstructor]
public MetadataViewModel(
IMetadataController metadataController,
IGatewayService gateway,
INavigationService navigator,
IImageResizerService imageResizer,
IEventAggregator eventAggregator
)
{
this.eventAggregator = eventAggregator;
this.imageResizer = imageResizer;
// populate drop-down lists
this.ProgramList = gateway.GetPrograms(true);
this.GenreList = gateway.GetGenres();
this.CopyrightList = gateway.GetCopyrights();
// add dummy values so the user can de-select
this.ProgramList.Add(new Program());
this.GenreList.Add(new Genre());
this.CopyrightList.Add(new Copyright());
// commands
this.SaveCommand = metadataController.SaveCommand;
this.CloseCommand = metadataController.CloseCommand;
this.DeleteCommand = metadataController.DeleteCommand;
this.SubmitCommand = metadataController.SubmitCommand;
this.UnSubmitCommand = metadataController.UnSubmitCommand;
this.LocationSearchCommand = new DelegateCommand<string>(this.LocationSearch);
this.SubjectSearchCommand = new DelegateCommand<string>(this.SubjectSearch);
this.RemoveLocationCommand = new DelegateCommand<Topic>(this.RemoveLocation);
this.RemoveSubjectCommand = new DelegateCommand<Topic>(this.RemoveSubject);
// events
this.eventAggregator.GetEvent<StorySelectedEvent>().Subscribe(OnStorySelected, ThreadOption.UIThread);
this.eventAggregator.GetEvent<AddLocationEvent>().Subscribe(OnAddLocation, ThreadOption.UIThread);
this.eventAggregator.GetEvent<AddSubjectEvent>().Subscribe(OnAddSubject, ThreadOption.UIThread);
this.eventAggregator.GetEvent<CommandCompletedEvent>().Subscribe(OnCommandCompleted, ThreadOption.UIThread);
this.eventAggregator.GetEvent<ImageResizeCompletedEvent>().Subscribe(OnImageResizeCompleted, ThreadOption.UIThread);
this.Initialize();
}
#endregion
private void OnStorySelected(StoryItem selectedStory)
{
if (this.selectedStory != null)
{
this.Initialize();
// override the initialized values
this.SelectedStory = selectedStory;
this.SelectedStory.HaveChanged = false;
this.HeaderInfo = "Edit";
}
}
public void OnAddLocation(Topic topic)
{
if (topic != null)
{
if (!this.SelectedStory.Locations.Contains(topic))
{
this.SelectedStory.Locations.Add(topic);
this.RaisePropertyChanged(() => this.SelectedStory.Locations);
}
}
}
public void OnAddSubject(Topic topic)
{
if (topic != null)
{
if (!this.SelectedStory.Subjects.Contains(topic))
{
this.SelectedStory.Subjects.Add(topic);
this.RaisePropertyChanged(() => this.SelectedStory.Subjects);
}
}
}
private void OnCommandCompleted(string commandType)
{
if (commandType == CommandTypes.MetadataEntry)
{
this.Initialize();
}
}
private void OnImageResizeCompleted(bool isSuccessful)
{
IsImageValid = false;
if (isSuccessful)
{
this.SelectedStory.KeyframeImages = true;
IsImageValid = true;
}
else
{
this.SelectedStory.KeyframeImages = false;
IsImageValid=false;
}
}
private void Initialize()
{
this.SelectedStory = new StoryItem();
this.HeaderInfo = "Create";
}
private void LocationSearch(object topicType)
{
this.eventAggregator.GetEvent<LocationSearchEvent>().Publish(null);
}
private void SubjectSearch(object topicType)
{
this.eventAggregator.GetEvent<SubjectSearchEvent>().Publish(null);
}
private void RemoveLocation(Topic selected)
{
if (selected != null)
{
// remove the primary too
if (this.SelectedStory.PrimaryLocation != null)
{
if (string.Equals(this.SelectedStory.PrimaryLocation.FullName, selected.FullName, StringComparison.InvariantCultureIgnoreCase))
{
this.SelectedStory.PrimaryLocation = new Topic();
}
}
bool isSuccessful = this.SelectedStory.Locations.Remove(selected);
if (isSuccessful)
{
this.RaisePropertyChanged(() => this.SelectedStory.Locations);
}
}
}
private void RemoveSubject(Topic selected)
{
if (selected != null)
{
// remove the primary too
if (this.SelectedStory.PrimarySubject != null)
{
if (string.Equals(this.SelectedStory.PrimarySubject.FullName, selected.FullName, StringComparison.InvariantCultureIgnoreCase))
{
this.SelectedStory.PrimarySubject = new Topic();
}
}
bool isSuccessful = this.SelectedStory.Subjects.Remove(selected);
if (isSuccessful)
{
this.RaisePropertyChanged(() => this.SelectedStory.Subjects);
}
}
}
}
private booly _isImageValid;
public bool IsImageValid
{
get
{
return _isImageValid;
}
set
{
_isImageValid = value;
this.RaisePropertyChanged(() => this.IsImageValid);
}
}
}
Honestly i don't know how view will understand the binding.
A standard approach is to have a boolean property like "IsImageValid" in your ViewModel...Then in your XAML, bind the Visibility property of your label to that property, with a BooleanToVisibilityConverter http://msdn.microsoft.com/en-us/library/system.windows.controls.booleantovisibilityconverter.aspx
<UserControl.Resources>
<BooleanToVisibilityConverter
x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
Then use it in one or more bindings like this:
<Label Visibility="{Binding IsImageValid,
Converter={StaticResource BooleanToVisibilityConverter}}"
......... />
please read this post first.
If you wanna display some text in your Label then your have to do the following steps:
add a Property to your Viewmodel
implement INotifyPropertyChanged in your Viewmodel and raise the event when ever the Property change
in your view set the DataContext to your Viewmodel instance
create a Binding in xaml to your property
thats all ;)
You don't have to see View from ViewModel, it is a basic principle behind MVVM pattern. View knows about VM, whereas VM does not know about View. Instead, you can do as #JeffN825 suggested, at least I'd recommend this as well.
Add the following to your user Control Property:
xmlns:VM="clr-namespace:<ProjectName>.ViewModels" //this place throws exception,what is <ProjectName> ?
For assigning the DataContext to the User Control use the following code if the DataContext is not already assigned:
<UserControl.DataContext> //where to add this part ?
<VM:MyViewModel>
</UserControl.DataContext>
Bind the Visibility of the label in the following manner:
Visibility="{Binding IsImageValid}" //this is done
Your ViewModel or ViewModelBase from which the VM is inherited should implement the INotifyPropertyChanged Interface:
namespace MyApp.ViewModels //this i have to do it at xaml.cs file or suppose to be in viewmodel ?
{
public class MyViewModel : INotifyPropertyChanged
{...
...
}
}
Declare the data member and Property in your VM like this:
private System.Windows.Visibility _isImageValid; //add this code in my viewmodel
public System.Windows.Visibility IsImageValid
{
get
{
return _isImageValid;
}
set
{
_isImageValid = value;
this.RaisePropertyChanged(() => this.IsImageValid);
}
}