How can I bind to a helper property in Silverlight - silverlight

For the sake of argument, here's a simple person class
public class Person : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register( "FirstName",
typeof ( string ),
typeof ( Person ),
null );
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register( "LastName",
typeof( string ),
typeof( Person ),
null );
public string FirstName
{
get
{
return ( string ) GetValue( FirstNameProperty );
}
set
{
SetValue( FirstNameProperty, value );
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs( "FirstName" ));
}
}
public string LastName
{
get
{
return ( string ) GetValue( LastNameProperty );
}
set
{
SetValue( LastNameProperty, value );
if ( PropertyChanged != null )
PropertyChanged( this, new PropertyChangedEventArgs( "LastName" ) );
}
}
}
I want to go about creating a readonly property like this
public string FullName
{
get { return FirstName + " " + LastName; }
}
How does binding work in this scenario? I've tried adding a DependancyProperty and raised the PropertyChanged event for the fullname. Basically I just want to have a property that I can bind to that returns the fullname of a user whenever the first or last name changes. Here's the final class I'm using with the modifications.
public class Person : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register( "FirstName",
typeof ( string ),
typeof ( Person ),
null );
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register( "LastName",
typeof( string ),
typeof( Person ),
null );
public static readonly DependencyProperty FullNameProperty =
DependencyProperty.Register( "FullName",
typeof( string ),
typeof( Person ),
null );
public string FirstName
{
get
{
return ( string ) GetValue( FirstNameProperty );
}
set
{
SetValue( FirstNameProperty, value );
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( "FirstName" ) );
PropertyChanged( this, new PropertyChangedEventArgs( "FullName" ) );
}
}
}
public string LastName
{
get
{
return ( string ) GetValue( LastNameProperty );
}
set
{
SetValue( LastNameProperty, value );
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( "LastName" ) );
PropertyChanged( this, new PropertyChangedEventArgs( "FullName" ) );
}
}
}
public string FullName
{
get { return GetValue( FirstNameProperty ) + " " + GetValue( LastNameProperty ); }
}
}

I'm not sure what you are trying to achieve here, but why is your Person class inheriting from DependencyObject and why are FirstName and LastName DependencyProperties? If all you want to do is bind the Person properties to user controls on your view, having the Person class implementing INotifyPropertyChanged is enough to make the data binding work. You will typically bind it to properties of a user control that are dependency properties (eg the Text property of a TextBlock).
Try this for you Person class:
using System.ComponentModel;
public class Person : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (value != _firstName)
{
_firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (value != _lastName)
{
_lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName
{
get { return FirstName + " " + LastName; }
}
}
And use it like this in your view:
<Grid x:Name="LayoutRoot" Background="White" >
<TextBlock Text="{Binding FullName}"/>
</Grid>
Then, in your codebehind, you could instantiate it like so:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new Person { FirstName = "John", LastName = "Doe" };
}
}
HTH,
Phil

First of all your implementation of the existing FirstName and LastName properties is flawed. The DependencyObject already has ways to inform bindings of changes to the values and values can be changed by other mechanism than calling the Setter methods.
My first question would be why are FirstName and LastName dependency properties at all? That seems like a strange choice for this type of class. Phil's answer has already provided what I would really expect the correct answer to be.
However in case your code was actually a simplification and that there is in fact a genuine need to create dependency properties here is how it should be done:-
public class Person : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register( "FirstName",
typeof ( string ),
typeof ( Person ),
OnNamePropertyChanged);
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register( "LastName",
typeof( string ),
typeof( Person ),
OnNamePropertyChanged);
private static void OnNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Person)d).OnNamePropertyChanged();
}
private void OnNamePropertyChanged()
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("FullName")));
}
public string FirstName
{
get { return GetValue(FirstNameProperty) as string; }
set { SetValue(FirstNameProperty, value); }
}
public string LastName
{
get { return GetValue(LastNameProperty) as string; }
set { SetValue(LastNameProperty, value); }
}
public string FullName
{
get { return FirstName + " " + LastName; }
}
}

Related

MVVM Datagrid Binding SelectedItem not updating

I'm new to WPF and MVVM and i've an applicaton that uses Entity Framework to connect to database and a datagrid to show the users of the application.
The users CRUD operations are made in a separate window and not in the datagrid.
My problems are related with the update of datagrid.
The insert operation is ok but the update is not.
View 1 (Users List):
<DataGrid Grid.Row="1"
ItemsSource="{Binding Users, Mode=TwoWay}"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False">
</DataGrid>
ViewModel :
class UserListViewModel: NotificationClass
{
UserDBContext _db = null;
public UserListViewModel()
{
_db = new UserDBContext();
Users = new ObservableCollection<User>(_db.User.ToList());
SelectedUser = Users.FirstOrDefault();
}
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
_users = value;
OnProprtyChanged();
}
}
private User _selectedUser;
public User SelectedUser
{
get
{
return _selectedUser;
}
set
{
_selectedUser = value;
OnProprtyChanged();
}
}
public RelayCommand Edit
{
get
{
return new RelayCommand(EditUser, true);
}
}
private void EditUser()
{
try
{
UserView view = new UserView();
view.DataContext = SelectedUser;
view.ShowDialog();
if (view.DialogResult.HasValue && view.DialogResult.Value)
{
if (SelectedUser.Id > 0){
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
updatedUser.Username = SelectedUser.Username; //this doesn't do nothing, object is already with the new username ?!
}
_db.SaveChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
after _db.SaveChanges(), datagrid should not be updated ?
Model:
class UserDBContext: DbContext
{
public UserDBContext() : base("name=DefaultConnection")
{
}
public DbSet<User> User { get; set; }
}
View 2 (User detail)
public partial class UserView : Window
{
public UserView()
{
InitializeComponent();
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
User object
class User: NotificationClass
{
public int Id { get; set; }
public string Username { get; set; }
public string CreatedBy { get; set; }
public DateTime? CreatedOn { get; set; }
}
NotificationClass
public class NotificationClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnProprtyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
if i close and open view 1, the new username is updated..
could someone help ? thanks
Just implementing INotifyPropertyChanged isn't enough, you have to explicitly invoke PropertyChanged (or in your case OnPropertyChanged) when a property changed.
See also https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
You can do it like so
class User : NotificationClass
{
private int _id;
private string _username;
private string _createdBy;
private DateTime? _createdOn;
public int Id
{
get => _id;
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Username
{
get => _username;
set
{
if (value == _username) return;
_username = value;
OnPropertyChanged();
}
}
public string CreatedBy
{
get => _createdBy;
set
{
if (value == _createdBy) return;
_createdBy = value;
OnPropertyChanged();
}
}
public DateTime? CreatedOn
{
get => _createdOn;
set
{
if (value.Equals(_createdOn)) return;
_createdOn = value;
OnPropertyChanged();
}
}
}
it worked ! many thanks #nosale !
what about the change made to SelectedUser being reflected in my context ?
if i do this :
SelectedUser.Username = "test";
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
i was thinking that SelectedUser object has the "test" username and updatedUser has the old username, but not .. updatedUser already have "test"

Evaluate Binding Path Dynamically in Silverlight in XAML

How can I evaluate a binding path dynamically, that is, the actual path comes from a property? For example, suppose I have a property of DisplayMemberPath, and I wanted to have something like:
Content="{Binding Path=(DisplayMemberPath)}"
I know that this doesn't work, it's just to explain what I wanted.
I know how to build a custom DataTemplate by code and set it dynamically, but that's not what I want.
You can use a custom behavior:
<SomeControl>
<Interaction.Behaviors>
<DynamicBindingBehavior TargetProperty="Content"
BindingPath="{Binding DisplayMemberPath}"/>
</Interaction.Behaviors>
...
</SomeControl>
and the code:
public class DynamicBindingBehavior : Behavior<DependencyObject>
{
private string m_targetProperty;
public string TargetProperty
{
get { return m_targetProperty; }
set
{
m_targetProperty = value;
TryFindTargetProperty();
}
}
private DependencyProperty m_targetDependencyProperty;
private void TryFindTargetProperty()
{
if (m_targetProperty == null || AssociatedObject == null)
{
m_targetDependencyProperty = null;
}
else
{
var targetDependencyPropertyInfo = AssociatedObject.GetType()
.GetProperty( TargetProperty + "Property", typeof( DependencyProperty ) );
m_targetDependencyProperty =
(DependencyProperty) targetDependencyPropertyInfo.GetValue(
AssociatedObject, null );
}
}
public string BindingPath
{
get { return (string) GetValue( BindingPathProperty ); }
set { SetValue( BindingPathProperty, value ); }
}
public static readonly DependencyProperty BindingPathProperty =
DependencyProperty.Register( "BindingPath", typeof( string ),
typeof( DynamicBindingBehavior ),
new PropertyMetadata( BindingPathChanged ) );
private static void BindingPathChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((DynamicBindingBehavior) d).BindingPathChanged();
}
private void BindingPathChanged()
{
if (m_targetDependencyProperty == null) return;
if (BindingPath == null)
{
AssociatedObject.ClearValue(m_targetDependencyProperty);
}
else
{
BindingOperations.SetBinding( AssociatedObject,
m_targetDependencyProperty, new Binding( BindingPath ) );
}
}
protected override void OnAttached()
{
base.OnAttached();
TryFindTargetProperty();
}
protected override void OnDetaching()
{
if (m_targetDependencyProperty != null)
AssociatedObject.ClearValue( m_targetDependencyProperty );
base.OnDetaching();
m_targetDependencyProperty = null;
}
}

How to Update Model from the View

I have Model Class Employee:
public class Employee
{
public string Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
and View Model class EmployeeViewModel which contains an Employee Object
public class EmployeeViewModel : INotifyPropertyChanged
{
public EmployeeViewModel()
{
}
private Employee currentEmployee;
public Employee CurrentEmployee
{
get { return this.currentEmployee; }
set
{
this.currentEmployee = value;
this.NotifyPropertyChanged("CurrentEmployee");
}
}
//Some code .....
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Now the View (WPF) will use Employee object in the View Model as ItemSource to display Employee data
Now the question is: I have Update button on the view and when I change the Employee properties in the view (via text boxes) I want to update the model (so afterward i can update the database), how to update this model from the view.
As I checked there something weird with your Model Class. It is the one that should implement INotifyPropertyChanged then create backing fields for each property something like this.
Model Class
public class Employee:INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name= value;
OnPropertyChanged("Name");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
**ViewModel Class**
class EmployeeViewModel
{
private IList<Employee> _employees;
public EmployeeViewModel()
{
_employees= new List<Employee>
{
new Employee{ID=1, Name ="Emp1"},
new Employee{ID=2, Name="Emp2"}
};
}
public IList<Employee> Employees
{
get
{
return _employees;
}
set
{
_employees= value;
}
}
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
}
#endregion
}
}
You may put your logic inside OnPropertyChanged event. This method is always called whenever you made changes on the UI
If you are using ObservableCollection modify your List by searching the item based on ID then if found do the modification of values. Whatever changes you have made will automatically affect the UI items if they are bound to your ObservableCollection then use the modified collection to update your DB records

How to catch a property changed event after binding

I have a custom UserControl:
public partial class CustomCtrl : UserControl
{
public CustomCtrl()
{
InitializeComponent();
}
public string Prova
{
get { return (string)GetValue(ProvaProperty); }
set
{
SetValue(ProvaProperty, value);
}
}
public static readonly DependencyProperty ProvaProperty =
DependencyProperty.Register("Prova", typeof(string), typeof(CustomCtrl));
}
I do this simple binding:
CustomCtrl c = new CustomCtrl();
TextBlock t = new TextBlock();
c.SetBinding(CustomCtrl.ProvaProperty, new Binding("Text") { Source = t });
t.Text = "new string";
Now c.Prova is "new string", but how can I catch in my CustomControl class the event informing me that Prova has changed?
Something like this (this will catch changes on all instances of CustomCtrl):
public static readonly DependencyProperty ProvaProperty =
DependencyProperty.Register(
"Prova",
typeof(string),
typeof(CustomCtrl),
new PropertyMetadata( new PropertyChangedCallback(OnValueChanged) )
);
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This is called whenever the Prova property has been changed.
}
If "clients" of your CustomCtrl wanted to catch a change to that property for a specific instance then they could use:
CustomCtrl instanceofsomecustomctrl = .......
DependencyPropertyDescriptor descr =
DependencyPropertyDescriptor.FromProperty(CustomCtrl.ProvaProperty, typeof(CustomCtrl));
if (descr != null)
{
descr.AddValueChanged(instanceofsomecustomctrl, delegate
{
// do something because property changed...
});
}
I think this is what you're looking for, you want an event onChangeHandler.
public partial class CustomCtrl : UserControl
{
public CustomCtrl()
{
InitializeComponent();
}
public string Prova
{
get { return (string)GetValue(ProvaProperty); }
set
{
SetValue(ProvaProperty, value);
OnPropertyChanged("Prova");
}
}
protected void OnPropertyChanged(string prova)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prova));
}
}
//This portion could go in the class where the event takes place
private delegate void UpdateDelegate(DependencyProperty dp, Object value);
}

Bind A ComboBox to a static Lookup table and bind the current Item to the user control class lookupID

I have a Static Class that supply me lookup tables (ObservableCollectoins) of a List.
(List of Cities for example)
public static GeneralData
{
public static ObservableCollection<City> colCity;
}
I have a UserControl with a combobox.
it's DataContext is another class (Person)
Person has a CityID
I want to bind the ComboBox to the Cities table and have it set the CurrentItem to the Person.CityID
When someone change the city (in the combo box) I want the Person CityID to be changed.
It is possible ?
(it looks like the problem is either to bind the combo box to the static class or to the person, but not both...)
This Code give me the combobox:
cbxCity.ItemsSource = GeneralData.colCity;
and This is the XAML - But how can I bind it to the Person CityID ?
<ComboBox Name="cbxCity" DisplayMemberPath="CityName" SelectedValuePath="CityID" SelectedItem="{Binding Path=CityID}" Width="80"></ComboBox>
This is certainly possible and could be done a few ways. The solution below uses an IValueConverter. Also note that I have overridden Equals and GetHashCode within the City class since the static class returning the listing of cities is creating a new instance each time.
The code behind is as follows and serves as an example and is in no way thoroughly tested; however it meets your needs...
public partial class Window1 : Window
{
private Data _data = new Data();
public Window1()
{
InitializeComponent();
this.DataContext = _data;
}
}
public class Data
{
private Person _person = new Person() { CityId = 3 };
public Person Person
{
get
{
return _person;
}
}
public ObservableCollection<City> Citys
{
get
{
return GeneralData.Citys;
}
}
}
public class Person : INotifyPropertyChanged
{
private int _cityId = -1;
public int CityId
{
get
{
return _cityId;
}
set
{
_cityId = value;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("CityId"));
Console.WriteLine("My new CityId is: " + _cityId);
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class City : INotifyPropertyChanged
{
private String _cityName = "N/A";
public String CityName
{
get
{
return _cityName;
}
set
{
_cityName = value;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("CityName"));
}
}
private int _cityId = -1;
public int CityId
{
get
{
return _cityId;
}
set
{
_cityId = value;
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("CityId"));
}
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
City c = obj as City;
if (c == null)
{
return false;
}
return this.CityId == c.CityId && String.Compare(this.CityName, c.CityName, true) == 0;
}
public override int GetHashCode()
{
return base.GetHashCode() ^ CityId;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public static class GeneralData
{
public static ObservableCollection<City> Citys
{
get
{
ObservableCollection<City> citites = new ObservableCollection<City>{
new City { CityId = 1, CityName = "Denver" },
new City { CityId = 2, CityName = "Phoenix" },
new City { CityId = 3, CityName = "San Diego" },
new City { CityId = 4, CityName = "Pasadena" },
new City { CityId = 5, CityName = "Sedona" }};
return citites;
}
}
}
public class CityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int cityId = (int)value;
return GeneralData.Citys.Single(i => i.CityId == cityId);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
City c = (City)value;
return c.CityId;
}
#endregion
}
The XAML is short and simple...
<Window.Resources>
<local:CityConverter x:Key="CityConverter"/>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Citys}"
DisplayMemberPath="CityName"
SelectedItem="{Binding Person.CityId, Converter={StaticResource CityConverter}}">
</ComboBox>
</Grid>
If you change SelectedItem in your XAMl to SelectedValue does it work?

Resources