Updating values in ObservableCollection - wpf

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.

Related

Using RegularExpression as attribute in .NET

I am testing use of regular expression as attribute in my application, but it is simply not working.
public partial class MainWindow : Window
{
[Required]
[RegularExpression(#"^[\d]+")]
public string number { get; set; }
public MainWindow()
{
InitializeComponent();
number = "sometext";
}
}
No error is being thrown and number accepts anything without caring for RegularExpression attribute.
How can I make number only to accept what is mentioned in regex? Usually I do validate in setter, but have learnt recently about attribute and wish to use it.
Thanks.
Your binding source must implement the IDataErrorInfo interface. Then you can set the ValidatesOnDataErrors and NotifyOnValidationError propeties on the binding.
See a simplified example below.
A base class to handle property changes and validation.
internal abstract class ValidatedObservableBase : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var results = new List<ValidationResult>();
var valid = Validator.TryValidateProperty(GetType().GetProperty(columnName)?.GetValue(this), new ValidationContext(this) { MemberName = columnName }, results);
return valid ? null : results[0].ErrorMessage;
}
}
public string Error
{
get => null;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The model, derived from the above base class.
internal class Model : ValidatedObservableBase
{
private string number;
[Required(ErrorMessage = "Required error")]
[RegularExpression(#"^[\d]+", ErrorMessage = "Regex error")]
public string Number
{
get => number;
set
{
number = value;
OnPropertyChanged();
}
}
}
A simple view model to set as the window's DataContext.
internal class ViewModel
{
public Model Model { get; set; } = new Model();
}
Lastly, the window.
<Window
...
xmlns:local="clr-namespace:Demo"
mc:Ignorable="d">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox
x:Name="TB"
Margin="24,24,24,0"
VerticalAlignment="Top"
Text="{Binding Path=Model.Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
<TextBlock
Margin="24,4,24,24"
Foreground="Red"
Text="{Binding ElementName=TB, Path=(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
</Window>
Thanks for comments. I modified code with some information on this site. myTextbox is bind with number and am using validation attribute. But still this is accepting everything that I write in my textbox.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myTextBox.DataContext = this;
}
[Required]
[AcceptNumberAttribute]
public string number { get; set; }
}
public sealed class AcceptNumberAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return new RegularExpressionAttribute(#"^[\d]$").IsValid(Convert.ToString(value).Trim());
}
}

WPF Combobox initial dictionary binding value not showing

I have a wpf combobox bound to a property LogicalP of a class SInstance. The ItemSource for the combobox is a dictionary that contains items of type LogicalP.
If I set LogicalP in SInstance to an initial state, the combobox text field shows empty. If I select the pulldown all my dictionary values are there. When I change the selection LogicalP in SInstance gets updated correctly. If I change Sinstance in C# the appropriate combobox value doesn't reflect the updated LogicalP from the pulldown.
I've set the binding mode to twoway with no luck. Any thoughts?
My Xaml:
<UserControl.Resources>
<ObjectDataProvider x:Key="PList"
ObjectType="{x:Type src:MainWindow}"
MethodName="GetLogPList"/>
</UserControl.Resources>
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource PList}}"
DisplayMemberPath ="Value.Name"
SelectedValuePath="Value"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
My C#:
public Dictionary<string, LogicalPType> LogPList { get; private set; }
public Dictionary<string, LogicalPType> GetLogPList()
{
return LogPList;
}
public class LogicalPType
{
public string Name { get; set; }
public string C { get; set; }
public string M { get; set; }
}
public class SInstance : INotifyPropertyChanged
{
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
They are not looking at the same source.
You need to have SInstance supply both the LogPList and LogicalP.
_LogicalP is not connected to LogPList
If you want to different objects to compare to equal then you need to override Equals.
Here's my working solution. By moving the dictionary retrieval GetLogPList to the same class as that supplies the data (as suggested by Blam) I was able to get the binding to work both ways. I changed binding to a list rather than a dictionary to simplify the combobox
Here's the updated Xaml showing the new ItemsSource binding and removal of the SelectedValuePath:
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding GetLogPList}"
DisplayMemberPath ="Name"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
I then changed the dictionary LogPList to static so that it would be accessible to the class SInstance:
public static Dictionary<string, LogicalPType> LogPList { get; private set; }
Finally, I moved GetLogPList to the class SInstance as a property. Note again it's returning a list as opposed to a dictionary to make the Xaml a little simpler:
public class SInstance : INotifyPropertyChanged
{
public List<LogicalPType> GetLogPList
{
get { return MainWindow.LogPList.Values.ToList(); }
set { }
}
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}

Silverlight DataContext Databinding behavior

I'll start off with a stripped-down/sanitized version of my code:
Model:
class DataObj : INotifyPropertyChanged {
// these actually call OnPropertyChanged, and have associated private variables
public string Name { get; set; }
public int Age { get; set; }
}
class DataContextObj : INotifyPropertyChanged {
public List<DataObj> DataItems { get; set; }
}
View:
<StackPanel x:Name="MyPanel">
<TextBlock Text="{Binding Path=DataItems[0].Name}" />
<TextBlock Text="{Binding Path=DataItems[0].Age}" />
</StackPanel>
View code-behind:
//in the constructor
MyPanel.DataContext = new DataContextObj();
Now, my question is, if the DataItems list is initialized but empty, what is the expected behavior when something tries to bind to, say, the first element in the list? My understanding is that it just ignores the binding; is that true?
Yes it will ignore the binding. If subsequently an item is added to the empty list the text blocks will not update since the binding expression associated with them will not know that the change happened.
The appropriate solution is to use:
public class DataContextObj
{
public ObservableCollection<DataObj> DataItems {get; private set; }
}
Additions to the collection will notify "Item[]" has changed which will allow the binding expression to re-evaluate.

Child item in TreeView not updating

I copied the basic method of having checkbox in a treeview from the official Silverlight toolkit Checkboxes in a TreeView example.
When a user clicks on a parent TreeViewItem I want all of the child items to be checked, as in the above example. This works fine when the parent is collapsed, clicking the checkbox puts a tick in the parent and when you expand the node all children have a tick in the checkbox.
However it doesn't work if the parent is expanded. None of the children are updated to have a tick in the checkbox, although the underlying data list is updated.
My XAML is as follows:
<sdk:HierarchicalDataTemplate x:Key="NodeTemplate" ItemsSource="{Binding Path=Contracts}">
<StackPanel Orientation="Horizontal" ToolTipService.ToolTip="{Binding Path=Name}">
<CheckBox IsTabStop="False" IsThreeState="{Binding Path=HasContracts}" IsChecked="{Binding Path=Selected, Mode=TwoWay}" Click="CheckBox_Click" />
<TextBlock Text="{Binding Path=Name}" Tag="{Binding Path=ID}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
<sdk:TreeView x:Name="tvClientContract" ItemsSource="{Binding Path=ClientContracts, Mode=TwoWay}" ItemTemplate="{StaticResource NodeTemplate}"/>
This is bound to a List<ClientContract> and uses the same code behind as in the linked example.
The ClientContract object is:
public int ID { get; set; }
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
How can I force the child to repaint itself as the underlying List<ClientContract> object is updated?
If you want to use INotifyPropertyChange(what I did instead of using ObservableCollection) here is how you do it per example on the ID element:
public class myclass : INotifyPropertyChanged
{
private int id_Value;
public int ID
{
get { return id_Value; }
set
{
id_Value = value;
NotifyPropertyChanged("ID");
}
}
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I hope this helps if it was what you were trying to do.
Try using ObservableCollection<ClientContract> instead of a List<>. Usually you want to databind to this collection type instead when the data is dynamic so it can notify the UI of collection changes.

WPF Data binding issue using mvvm pattern

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

Resources