I have created a user control "SearchControl"(which will be reused further in other screens as well.
SearchControl ->
<usercontrol name="SearchControl"......>
<stackpanel orientation="horizontal"...>
<TextBox Text"{Binding Path=UserId}"...>
<Button Content="_Search" ....Command="{Binding Path=SearchCommand}"..>
</stackpanel>
</usercontrol>
public partial class SearchControl : UserControl
{
public SearchControl()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
I then use this control in a window "UserSearch"
<window name="UserSearch".............
xmlns:Views="Namespace.....Views">
<Grid>
<Grid.RowDefinitions>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition..../>
<ColumnDefinition..../>
</Grid.ColumnDefinitions>
<Views:SearchControl Grid.Row="0" Grid.Colspan="2"/>
<TextBlock Text="User Id" Grid.Row="1" Grid.Column="0"..../>
<TextBox Text="{Binding Path=UserId}" Grid.Row="1" Grid.Column="1".../>
<TextBlock Text="First Name" Grid.Row="2" Grid.Column="0"..../>
<TextBox Text="{Binding Path=FirstName}" Grid.Row="2" Grid.Column="1".../>
<TextBlock Text="Last Name" Grid.Row="3" Grid.Column="0"..../>
<TextBox Text="{Binding Path=LastName}" Grid.Row="3" Grid.Column="1".../>
</Grid>
</window>
public partial class UserSearch : Window
{
public UserSearch()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
What I am aimimg for:
When I enter UserId inthe textbox in SearchControl and click on Search button, the resulting record which is retieved should be displayed in the textboxes for UserId, FirstName, LastName
class UserViewModel:INotifyPropertyChanged
{
DBEntities _ent; //ADO.Net Entity set
RelayCommand _searchCommand;
public UserViewModel()
{
_ent = new DBEntities();
}
public string UserId {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public ICommand SearchCommand
{
get
{
if(_searchCommand == null)
{
_searchCommand = new RelayCommand(param = > this.Search());
}
return _searchCommand;
}
}
public void Search()
{
User usr = (from u in _ent
where u.UserId = UserId
select u).FirstOrDefault<User>();
UserId = usr.UserId;
FirstName = usr.FirstName;
LastName = usr.LastName;
OnPropertyChanged("UserId");
OnPropertyChanged("FirstName");
OnPropertyChanged("LastName");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertChangedEventArgs(propertyName);
}
}
Here as I am using two separate instances of the UserViewModel for the SearchControl and UserSearch, even though I retieve the record for the particular user on searching by UserId, I am unable to bind the properties UserId, FullName , LastName with the respective textboxes...How do I fix this problem??
1) Don't let the View initialize the presentation model, it should be the other way round. The presentation model is the object of interest, not the particular view.
public interface IView
{
void SetModel(IPresentationModel model);
}
publiv class View : UserControl, IView
{
public void SetModel(IPresentationModel model)
{
DataContext = model;
}
}
public class PresentationModel : IPresentationModel
{
public PresentationModel(IView view)
{
view.SetModel(this);
}
}
2) Don't set the data context of the subview in the code behind file. Usually, the view that uses the subview sets the data context in the xaml file.
3) Usually each view has its own presentation model. The presentation model should have one type of view. That means that different views of a single presentation model may differ in appearance but not in functionality (in your case one view is used to search, the other one is used to display and edit data). So, you have vialoted the Single Responsibilty Principle.
4) Abstract your data access layer, otherwise you won't be able to unit test your presentation model (because it needs access to the data base directly). Define an repository interface and implementation:
public interface IUserRepository
{
User GetById(int id);
}
public class EntityFrameworkUserRepository : IUserRepository
{
private readonly DBEntities _entities;
public EntityFrameworkUserRepository(DBEntities entities)
{
_entities = entities;
}
public User GetById(int id)
{
return _entities.SingleOrDefault(u => u.UserId == id);
}
}
5) Don't use FirstOrDefault because an ID is unique, so there must not be several users for one id. SingleOrDefault (used in the code snippet above) throws an exception if more than one result is found but returns null if none is found.
6) Bind directly to your entity:
public interface IPresentationModel
{
User User { get; }
}
<StackPanel DataContext="{Binding Path=User}">
<TextBox Text="{Binding Path=FirstName}" />
<TextBox Text="{Binding Path=LastName}" />
</StackPanel>
7) Use the CommandParameter to provide the user id you are searching for directly with your command.
<TextBox x:Name="UserIdTextBox">
<Button Content="Search" Command="{Binding Path=SearchCommand}"
CommandParameter="{Binding ElementName=UserIdTextBox, Path=Text}" />
public class PresentationModel
{
public ICommand SearchCommand
{
// DelegateCommand<> is implemented in some of Microsoft.BestPractices
// assemblies, but you can easily implement it yourself.
get { return new DelegateCommand<int>(Search); }
}
private void Search(int userId)
{
_userRepository.GetById(userId);
}
}
8) If only data binding causes issues, look at the following website to get some ideas how to debug wpf data bindings: http://beacosta.com/blog/?p=52
9) Don't use strings that contain property names. Once you refactor your code and properties change their names, to will have a stressful time finding all property names in strings and fixing them. Use lambda expressions instead:
public class PresentationModel : INotifiyPropertyChanged
{
private string _value;
public string Value
{
get { return _value; }
set
{
if (value == _value) return;
_value = value;
RaisePropertyChanged(x => x.Value);
}
}
public PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(Expression<Func<PresentationModel, object>> expression)
{
if (PropertyChanged == null) return;
var memberName = ((MemberExpression)expression.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
I wish you the best to solve your problem and I hope that I could help you a little bit.
Best Regards
Oliver Hanappi
Related
Hey I have an ObservableCollection which consists of a class with two attributes (strings = User and Response) bound to a listbox.
I would like to have the users in the listbox first, which I add with this:
for (int i = 0; i < ArrStrUser.Length; i++)
{
Users.Add(new User() { input = ArrStrUser[i].Trim() });
}
I want to add the responses to the respective user later.
If I do this, they will be added to the ObservableCollection but not update in the listbox.
Users[i].response = strOutput.Trim().Replace(Environment.NewLine, " ");
The ObservableCollecton
private ObservableCollection<Input> Users = new ObservableCollection<Input>();
The Class:
public class Input
{
public string user{ get; set; }
public string response { get; set; }
}
XAML:
<ListBox x:Name="LBresponse" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource UserTemplate}" />
<DataTemplate x:Key="UserTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path= user}" Width="50"/>
<TextBlock Text="{Binding Path= response}" />
<Button Content="Delete" Click="DeleteUser_Clicked" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
Simple solution
Your Input class needs to implement the INotifyPropertyChanged interface and invoke the PropertyChanged event upon changing property's value in order to update the ListBox. The ObservableCollection only "cares" about adding or removing items, it doesn't handle item's property changing.
Try editing your input class like this:
public class Input : INotifyPropertyChanged
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set {
_response = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now changing the Response property should update the UI.
Better solution
I'd also advise you to separate the INotifyPropertyChanged implementation into its own class if you want to use it somewhere else, too. Or better yet, use a library that already has it, like the mvvm-helpers nuget package by James Montemagno.
Here's a link to the INotifyPropertyChanged implementation from that library
This is how you use it:
public class Input : ObservableObject
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set => SetProperty(ref _response, value);
}
}
It also supports passing in an OnChanged Action and a validation function.
I am new to WPF but have an small understanding of MVVM, so far this is what I have implemented.
UpdateTableView - View (Short snippet of larger user control)
<UserContol.DataContext>
<local:UpdateTableViewModel />
</UserContol.DataContext>
<StackPanel>
<TextBox Text="{Binding InputPath}"/>
<TextBlock Content="Placeholder" />
</StackPanel>
UpdateTableModel - Model
public class UpdateTableModel : ObservableObject
{
private string _inputPath;
public string InputPath
{
get
{
return _inputPath;
}
set
{
if (value != _inputPath)
{
_inputPath = value;
OnPropertyChanged("InputPath");
}
}
}
}
ObservableObject
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanaged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanaged;
if (handler != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And an empty UpdateTableViewModel
class UpdateTableViewModel : ObservableObject { }
My question is how would I use data binding so that as a user when I enter a inputPath in the text box, firstly whatever I type is store in the property _inputPath so I can use it in code behind and additionally be reflected in the text block.
I have done some research and found about one way and two way data binding and can't really work out what else I need to add for my desired functionality.
Thanks in advance.
Your view models must contain the properties you want to bind to.
Generally the TextBox.Text property automatically binds TwoWay. This is the default behavior. So, without specifying the Binding.Mode explicitly, the text entered into the TextBox will be automatically sent to the binding source. In your case the input would be automatically sent to the InputPath property.
UpdateTableModel.cs
public class UpdateTableModel
{
public void SaveUserNameToFile(string filePath, string userName)
{
File.AppendAllText(filePath, userName, Encoding.UTF8);
}
}
UpdateTableViewModel.cs
An implementation of RelayCommand can be found at
Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern - Relaying Command Logic
class UpdateTableViewModel : ObservableObject
{
private UpdateTableModel UpdateTableModel { get; }
public ICommand SaveUserCommand => new RelayCommand(SaveUserName);
private string _userName;
public string UserName
{
get => _userName;
set
{
if (value != _userName)
{
_userName = value;
OnPropertyChanged(nameof(UserName));
}
}
}
private string _inputPath;
public string InputPath
{
get => _inputPath;
set
{
if (value != _inputPath)
{
_inputPath = value;
OnPropertyChanged(nameof(InputPath));
}
}
}
public UpdateTableViewModel()
{
this.UpdateTableModel = new UpdateTableModel();
}
// Alternative constructor
public UpdateTableViewModel(UpdateTableModel updateTableModel)
{
this.UpdateTableModel = updateTableModel;
}
private void SaveUserName(object param)
{
// Pass the data to the model
this.UpdateTableModel.SaveUserNameToFile(this.InputPath, this.UserName);
}
}
UpdateTableView.xaml
<UserControl>
<UserContol.DataContext>
<local:UpdateTableViewModel />
</UserContol.DataContext>
<StackPanel>
<TextBox Text="{Binding UserName}" />
<TextBox Text="{Binding InputPath}" />
<Button Command="{Binding SaveUserCommand}"
Content="Save to File" />
</StackPanel>
</UserControl>
I have view class which is called Client, its view model is ClientViewModel. ClientViewModel is has a model object ClientInfo. This ClientInfo [Model] is complex object, which has properties of Model classes called Client & ClientProfile.
I have bound the properties of my UI elements in View, like as follows, (I use xxx.yyy.zzz to get to the property)
<Label Content="First Name:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3,5,0,4" VerticalAlignment="Center" Height="26" Width="70" />
<TextBox Grid.Column="1" Grid.Row="1" Height="24" HorizontalAlignment="Left" Margin="3,7,0,4" Name="firstNameTextBox" Text="{Binding Path=ClientInfo.Client.FirstName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<Label Content="Last Name:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3,3,0,6" VerticalAlignment="Center" Height="26" Width="69" />
<TextBox Grid.Column="1" Grid.Row="2" Height="24" HorizontalAlignment="Left" Margin="3,5,0,6" Name="lastNameTextBox" Text="{Binding Path=ClientInfo.Client.LastName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
..
<Button Content="Save" Height="24" Grid.Column="0" Grid.Row="0" Command="{Binding SubmitCommand}" Cursor="Hand" Margin="549,10,10,0" Name="button1" VerticalAlignment="Top" Width="75" RenderTransformOrigin="-0.137,-1.804" />
ClientViewModel:
[Export(typeof(ClientViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClientViewModel : NotificationObject
{
private readonly IClientService clientService;
private ClientInfo clientInfoModel;
private string currentState;
public DelegateCommand<object> SubmitCommand { get; private set; }
public DelegateCommand<object> UpdateCommand { get; private set; }
public DelegateCommand<object> LoadCommand { get; private set; }
[Import]
public ClientInfo ClientInfoModel
{
get { return this.clientInfoModel; }
set
{
clientInfoModel = value;
this.RaisePropertyChanged(() => this.ClientInfoModel);
}
}
[ImportingConstructor]
public ClientViewModel(IClientService clientService)
{
this.clientService = clientService;
this.SubmitCommand = new DelegateCommand<object>(this.Submit);
this.UpdateCommand = new DelegateCommand<object>(this.Update);
this.LoadCommand = new DelegateCommand<object>(this.Load);
}
private void Load(object obj)
{
throw new NotImplementedException();
}
private void Update(object obj)
{
//update
throw new NotImplementedException();
}
private void Submit(object obj)
{
string s = this.ClientInfoModel.ClientBasic.FirstName;//<--- this where i get the NPE exception
}
public string ViewName
{
get { return "Client Details"; }
}
public string CurrentState
{
get
{
return this.currentState;
}
set
{
if (this.currentState == value)
{
return;
}
this.currentState = value;
this.RaisePropertyChanged(() => this.CurrentState);
}
}
public bool CanSubmit
{
get { return true; }
}
public void Submit()
{
this.CurrentState = "Submitting";
//this.clientRepository.SaveClientAsync(this.ClientInfoModel, result => { SaveClient(); });
}
private object SaveClient()
{
this.CurrentState = "Saving";
return null;
}
}
ClientInfo (Model):
public class ClientInfo : DomainObject
{
public Client ClientBasic { get; set; }
public ClientProfile Profile { get; set; }
}
Client (Model):
public class Client : DomainObject
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
....
}
In submit command invocation:
private void Submit(object obj)
{
ClientInfo ci = new ClientInfo();
ci.Client <-- (here i would want to get the new Client obj assigned from properties?)
ci.ClientProfile <---(same as above)
}
View has submit button to save, on save command. I have to save new client object calling some services.
Issue here is that, I need to fill ClientInfo model with new Client() & new ClientProfile() objects. How can do that with this setup I am having.
I can see a few points here which I would do differently. However, in general with what you've posted everything is fine, there must be some mistake in the bits that you haven't posted. Please post the complete ViewModel class and explain how you pass the M to the VM and the VM to the V and I'll have another look at it.
If ClientInfo is null when Submit(..) is executed, that means that your ViewModel doesn't have a model. There must be something wrong with the assignment of the ClientInfo on your ViewModel. Try to set a breakpoint in ClientInfos set accessor and see whether it is set once and only once. Try to set a breakpoint in, for example, FirstNames set accessor and see whether it gets hit when you enter the name in your UI. Are there any BindingErrors shown in the output console?
That being said, are you sure that you want to do what you are trying at all? If you create a new ClientInfo class, and assign the properties Client and ClientProfile from another ClientInfo class, your two ClientInfo objects point to the exact same Client and ClientProfile object. As ClientInfo only has these two properties, I can think of reason why you would duplicate the ClientInfo object. you can just as good use the original object which is your ViewModel's Model...
Secondly, your ViewModel exposes the Model directly, which is not actually the point of a ViewModel, especially when you end up with chained bindings like
Text="{Binding Path=ClientInfo.Client.FirstName}"
The ViewModels core competency is to aggregate the data and allow easy binding from the View. I would expose a FirstName, LastName, etc. property on the ViewModel and let the ViewModel figure out where to take the data from and push the data to. Remember that you want to keep the View independent of any implementation details in the background.
Maybe these two suggestions already solve or avoid the problem altogether. Otherwise please feel free to post more context and I'll have a look again.
EDIT
In your ViewModel, I would expect something like
ClientInfoModel = clientService.GetClientInfo(...);
You inject the service, but where do you initialize clientInfoModel?
Say for example I have the following type:
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
The above type can be assigned to be held in a Propety in a ViewModel like so assuming a corresponding backing field has been created but omitted here ofc:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
// raise property changed etc
}
}
In my xaml a straight forward binding would be:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
Text="{Binding MessageToDisplay,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Can you extend a binding by using the dot notation syntax? e.g:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
**Text="{Binding SelectedSite.Name,**
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Seems like a an interesting feature but my gut instinct is a no as my DC is being assigned at RunTime so at DesignTime or CompileTime, I can't see any clues that could make this feature work or not?
Correct me if have misunderstood what a complex object is, I have simplified mine down for the sake of this question.
Of course this is possible. However, WPF needs to know when any property along the path has changed. To that end, you need to implement INotifyPropertyChanged (or other supported mechanisms). In your example, both Site and the VM containing SelectedSite should implement change notification).
Here's how you could implement the functionality you specified in your question:
// simple DTO
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
// base class for view models
public abstract class ViewModel
{
// see http://kentb.blogspot.co.uk/2009/04/mvvm-infrastructure-viewmodel.html for an example
}
public class SiteViewModel : ViewModel
{
private readonly Site site;
public SiteViewModel(Site site)
{
this.site = site;
}
// this is what your view binds to
public string Name
{
get { return this.site.Name; }
set
{
if (this.site.Name != value)
{
this.site.Name = value;
this.OnPropertyChanged(() => this.Name);
}
}
}
// other properties
}
public class SitesViewModel : ViewModel
{
private readonly ICollection<SiteViewModel> sites;
private SiteViewModel selectedSite;
public SitesViewModel()
{
this.sites = ...;
}
public ICollection<SiteViewModel> Sites
{
get { return this.sites; }
}
public SiteViewModel SelectedSite
{
get { return this.selectedSite; }
set
{
if (this.selectedSite != value)
{
this.selectedSite = value;
this.OnPropertyChanged(() => this.SelectedSite);
}
}
}
}
And your view might look something like this (assuming a DataContext of type SitesViewModel):
<ListBox ItemsSource="{Binding Sites}" SelectedItem="{Binding SelectedSite}"/>
Below is what worked for me:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
RaisePropertyChanged("SelectedSite");
}
}
In my xaml I was able to do:
<TextBox Name="tbSiteName"
Width="250"
Height="30"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsReadOnly="True"
Style="{StaticResource MainTextBoxStyle}"
Text="{Binding SelectedSite.Name,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
This allows you to access data members off the Site Type without having to create individual properties that wrap each data member on the Site Type. Then individual controls can bind to each property declared in the VM. In a one to one fashion, this aproach can become rather verbose. The binding extension attached to the Text property of the TextBox control shown above, shows that we are not binding to a simple straight forward property but actually to a custom type. Potentially removing the need to create more public properties.
I've got a small test WPF MVVM application working in which a view allows the user to change the first or last names of customers and the full name automatically changes, so communication is going from M-to-MV-to-V and back, everything is fully decoupled, so far so good.
But now as I look to how I will begin extending this to build large applications with the MVVM pattern, I find the decoupling to be an obstacle, namely:
how will I do validation messages, e.g. if back in the model in the LastName setter I add code that prevents names over 50 characters from being set, how can I send a messsage to the view telling it to display a message that the name was too long?
in complex applications I may have dozens of views on a screen at one time, yet I understand that in MVVM each view has one and only one ViewModel assigned to it to provide it with data and behavior, so how is it that the views can interact with each other, e.g. in the above validation example, what if back in the customer model we want to inform a particular "MessageAreaView" to display the message "Last name may only contain 50 characters.", how to we communicate that up the stack to that particular view?
CustomerHeaderView.xaml (View):
<UserControl x:Class="TestMvvm444.Views.CustomerHeaderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<StackPanel HorizontalAlignment="Left">
<ItemsControl ItemsSource="{Binding Path=Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<TextBox
Text="{Binding Path=FirstName, Mode=TwoWay}"
Width="100"
Margin="3 5 3 5"/>
<TextBox
Text="{Binding Path=LastName, Mode=TwoWay}"
Width="100"
Margin="0 5 3 5"/>
<TextBlock
Text="{Binding Path=FullName, Mode=OneWay}"
Margin="0 5 3 5"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Customer.cs (Model):
using System.ComponentModel;
namespace TestMvvm444.Model
{
class Customer : INotifyPropertyChanged
{
public int ID { get; set; }
public int NumberOfContracts { get; set; }
private string firstName;
private string lastName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName != value)
{
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName
{
get { return lastName; }
set
{
if (lastName != value)
{
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName
{
get { return firstName + " " + lastName; }
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
}
For validation, have your view model implement IDataErrorInfo. As for communication between views, don't be afraid to write UI services (eg. a message service that allows and view model to contribute messages that will be displayed somewhere in the UI). Or if there is a hard relationship between view models (eg. one view model owns another) then the owning view model can hold a reference to the child view model.
A really simple way to add validation messages is to use binding.
Add an notifable property to your view model that defines whether the validation message should be displayed or not:
private Boolean _itemValidatorDisplayed;
public Boolean ItemValidatorDisplayed
{
get { return _itemValidatorDisplayed; }
set
{
_itemValidatorDisplayed= value;
_OnPropertyChanged("ItemValidatorDisplayed");
}
}
Add a convertor class that converts bool to visibility:
using System;
using System.Windows;
namespace xxx
{
public class BoolToVisibilityConverter : IValueConverter
{
public bool Negate { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool val = System.Convert.ToBoolean(value);
if (!Negate)
{
return val ? Visibility.Visible : Visibility.Collapsed;
}
else
{
return val ? Visibility.Collapsed : Visibility.Visible;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Bind to the property from the view and apply the converter:
<UserControl x:Class="ViewClass"
...
>
<UserControl.Resources>
<contract:BoolToVisibilityConverter Negate="False"
x:Key="BoolToVisibilityConverter" />
</UserControl.Resources>
...
<TextBlock Visibility="{Binding Converter={StaticResource BoolToVisibilityConverter}, Path=ItemValidatorDisplayed}" />
...
</UserControl>
You'll need to be setting the ViewModel as the datacontext of the view:
namespace xxx
{
public partial class ViewClass: UserControl
{
public ViewClass()
{
InitializeComponent();
this.DataContext = new ViewClass_ViewModel();
}
}
}
Bingo - perfectly working validation being pushed to any view that cares to subscribe to this ViewModel / Property.
You will also be able to bind validation to the source object collection in SL3 :-)