My goal is to bind to a set of objects in WPF. I first tried to use generics but horribly failed at it. Since I can't cast generic properties to INotifyProperty interfaces, to hook up the changedevent.
So I came up with this class to use as an object in my BindingList. ( Yes, I need those to notify me when an object within the list changes ). So I need your opinion, improvements on my code.
public class GPair : ObservableObject
{
public GPair()
{
}
private ObservableObject _first;
public ObservableObject First
{
get
{
return this._first;
}
set
{
this._first = value;
((ObservableObject)value).PropertyChanged +=new PropertyChangedEventHandler(First_PropertyChanged);
RaisePropertyChanged("First");
}
}
private ObservableObject _second;
public ObservableObject Second
{
get
{
return this._second;
}
set
{
this._second = value;
((ObservableObject)value).PropertyChanged += new PropertyChangedEventHandler(Second_PropertyChanged);
RaisePropertyChanged("Second");
}
}
private void First_PropertyChanged(object sender, PropertyChangedEventArgs args)
{
RaisePropertyChanged("First");
}
private void Second_PropertyChanged(object sender, PropertyChangedEventArgs args)
{
RaisePropertyChanged("Second");
}
}
The ObservableObject is just a helperclass that implements the INotifyPropertyChanged.
This piece of code allows me couple two objects in a binding... The Binding itself would look like this:
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=First.ObjectPropertie}" />
<TextBlock Text="{Binding Path=Second.ObjectPropertie}" />
</StackPanel>
Any opinions or improvements??
Kind regards!
I don't fully get what you are trying to achieve, but the GPair class looks odd
What about having something like this?
public class ObservablePair : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private MyObject m_First;
public MyObject First
{
get { return m_First; }
set
{
m_First = value;
OnPropertyChanged("First");
}
}
private MyObject m_Second;
public MyObject Second
{
get { return m_Second; }
set
{
m_Second = value;
OnPropertyChanged("Second");
}
}
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new
PropertyChangedEventArgs(propertyName)); ;
}
}
HTH
Here's my take on the answer:
public class ObservablePair<T1, T2> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private T1 _item1;
public T1 Item1
{
get => _item1;
set => OnPropertyChanged(nameof(Item1), _item1 = value);
}
private T2 _item2;
public T2 Item2
{
get => _item2;
set => OnPropertyChanged(nameof(Item2), _item2 = value);
}
public void OnPropertyChanged<T>(string propertyName, T _)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Related
I have this Base class:
public abstract class WiresharkFile : BaseObservableObject, IDisposable
{
private string _fileName;
private int _packets;
private int _packetsSent;
public string FileName
{
get { return _fileName; }
set { _fileName = value; }
}
public int Packets
{
get { return _packets; }
set { _packets = value; }
}
public int PacketsSent
{
get { return _packetsSent; }
set
{
_packetsSent = value;
OnPropertyChanged();
}
}
public void Dispose()
{
// Implemented insde inherit classes.
}
}
BaseObservableObject:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
My collection:
public ObservableCollection<WiresharkFile> wiresharkFiles { get; set; }
So as you can see avery inherit class from my Base class have this PacketsSent property change so in this case all works fine.
Now i have another static property inside WiresharkFile (base class):
private static volatile int _totalPacketsSent;
public static int TotalPacketsSent
{
get { return _totalPacketsSent; }
set
{
_totalPacketsSent = value;
OnStaticPropertyChanged();
}
}
So inside BaseObservableObject i created this member:
public static event PropertyChangedEventHandler StaticPropertyChanged;
And:
protected static void OnStaticPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = StaticPropertyChanged;
if (handler != null) handler(typeof(WiresharkFile), new PropertyChangedEventArgs(propertyName));
}
And inside XAML i want to update my Label:
Content="{Binding Path=(my:WiresharkFile.TotalPacketsSent)}"
So this is not working so currently this Label is updated via code behind.
As i doing something wrong ?
The static property changed event has to be declared in class WiresharkFile (i.e. the class that also declares the static property). It won't work if it is declared in a base class.
public class WiresharkFile : BaseObservableObject
{
public static event PropertyChangedEventHandler StaticPropertyChanged;
private static void OnStaticPropertyChanged(string propertyName)
{
var handler = StaticPropertyChanged;
if (handler != null)
{
handler(null, new PropertyChangedEventArgs(propertyName));
}
}
// static property here
}
Since your create PropertyChangedEventHandler is no part of a real INotifyPropertyChanged interface, the binding doesn't recognize that the notification event was thrown, thus it doesn't pull any new value from the TotalPacketsSent. To make binding refresh itself try something like this:
Put in your WiresharkFile class:
private static WiresharkFile This;
public WiresharkFile()
{
This = this;
}
private static volatile int _totalPacketsSent;
public static int TotalPacketsSent
{
get { return _totalPacketsSent; }
set
{
_totalPacketsSent = value;
OnStaticPropertyChanged(This);
}
}
- Handler code:
protected static void OnStaticPropertyChanged(object sender, [CallerMemberName]string propertyName = null)
{
var baseObservable = sender as BaseObservableObject;
if(baseObservable == null) return;
baseObservable.OnPropertyChanged(propertyName);
}
in my opinion you doesn't need the PropertyChangedEventHandler event there in BaseObservableObject class.
regards,
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
I have a need to switch the view being displayed based on a certain condition.
I have implemented the switching logic in the constructor of the ViewModel
I am implementing IRegionMemberLifetime on the View and setting KeepAlive to false so that I always get a new instance of the View and the ViewModel.
But for some reason, when I click on the Navigation Button, my breakpoint at KeepAlive never reaches and I get the MainView instead of the WelcomeView.
Here is the code for your reference:
Navigation Button:
<Controls:SignedButton VerticalAlignment="Top" Width="275" Height="45"
Foreground="#FFFFFF"
LeftSign="<" Text="Back to Accounts"
TextSize="20" ButtonBackground="#666666"
HoverBackground="#0FBDAC" HoverOpacity="1" Margin="0,25,0,0"
Command="{x:Static Infrastructure:ApplicationCommands.NavigateCommand}"
CommandParameter="{x:Type Views:MainView}"/>
View Model:
[RegionMemberLifetime(KeepAlive = false)]
public class MainViewModel : ViewModel, IMainViewModel
{
private readonly IUnityContainer _container;
private readonly IRegionManager _regionManager;
public MainViewModel(IUnityContainer container, IRegionManager regionManager)
{
_container = container;
_regionManager = regionManager;
Accounts = new List<Account>();
if (Accounts.Any()) return;
IRegion region = _regionManager.Regions[Regions.Main];
var views = region.Views;
foreach (var view in views)
{
region.Remove(view);
}
region.Add(_container.Resolve<IWelcomeView>());
}
public IList<Account> Accounts { get; private set; }
}
View Model Base:
public abstract class ViewModel : IViewModel
{
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
View:
[RegionMemberLifetime(KeepAlive = false)]
public partial class MainView : UserControl, IMainView
{
public MainView(IMainViewModel mainViewModel)
{
InitializeComponent();
ViewModel = mainViewModel;
}
public IViewModel ViewModel
{
get { return (IViewModel) DataContext; }
set { DataContext = value; }
}
}
Shell View Model:
public class ShellViewModel : ViewModel, IShellViewModel
{
private readonly IRegionManager _regionManager;
public ShellViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<object>(Navigate);
ApplicationCommands.NavigateCommand.RegisterCommand(NavigateCommand);
}
private void Navigate(object navigatePath)
{
if (navigatePath != null)
{
_regionManager.RequestNavigate(Regions.Main, navigatePath.ToString());
}
}
public DelegateCommand<object> NavigateCommand { get; private set; }
}
It would be useful for you to look into the RegionMemberLifetimeBehavior class in the Prism Library (for me, it is at C:\Prism4\PrismLibrary\Desktop\Prism\Regions\Behaviors)
Both the IRegionMemberLifetime interface and the RegionMemberLifetimeAttribute accomplish the same thing and can be defined on either your View or your ViewModel (provided the viewmodel is set to DataContext).
Here's the code that is relevant:
private void OnActiveViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// We only pay attention to items removed from the ActiveViews list.
// Thus, we expect that any ICollectionView implementation would
// always raise a remove and we don't handle any resets
// unless we wanted to start tracking views that used to be active.
if (e.Action != NotifyCollectionChangedAction.Remove) return;
var inactiveViews = e.OldItems;
foreach (var inactiveView in inactiveViews)
{
if (!ShouldKeepAlive(inactiveView))
{
this.Region.Remove(inactiveView);
}
}
}
private static bool ShouldKeepAlive(object inactiveView)
{
IRegionMemberLifetime lifetime = GetItemOrContextLifetime(inactiveView);
if (lifetime != null)
{
return lifetime.KeepAlive;
}
RegionMemberLifetimeAttribute lifetimeAttribute = GetItemOrContextLifetimeAttribute(inactiveView);
if (lifetimeAttribute != null)
{
return lifetimeAttribute.KeepAlive;
}
return true;
}
I was able to even step this code to see how it interacts with my application. To answer your question, if your KeepAlive property is not getting hit, then it is not being removed from the ActiveViews. Also make sure that if you are resolving your view from a container through IoC that you have not registered it as a singleton type (that you get a new instance each time it is resolved). The attribute/interface will only remove it completely from the region, not guarantee you get a fresh instance.
I've a ViewModel class like this in a Prism / WPF project.
public class ContentViewModel : ViewModelBase, IContentViewModel
{
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
SaveCommand = new DelegateCommand(Save, CanSave);
}
public Person Person { get; set; }
public DelegateCommand SaveCommand { get; set; }
private void Save()
{
// Save actions here...
}
private bool CanSave()
{
return Person.Error == null;
}
}
The person type used in the above ViewModel is defined as follows:
public class Person : INotifyPropertyChanged, IDataErrorInfo
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
// other properties are implemented in the same way as above...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _error;
public string Error
{
get
{
return _error;
}
}
public string this[string columnName]
{
get
{
_error = null;
switch (columnName)
{
// logic here to validate columns...
}
return _error;
}
}
}
An instance of ContentViewModel is set as DataContext of a View. Inside the View I've used binding to Person as follows:
<TextBox Text="{Binding Person.FirstName, ValidatesOnDataErrors=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
When I make changes to TextBox which is binded to Person's properties like FirstName and click Save I could see the changes in ViewModel command handler. But if any of these properties fail in validation CanSave is never executed and button never gets disabled.
How do I disable a button based on DelegateCommand's CanExecute action handler in the above scenario?
In the constructor of ContentViewModel add this line
public ContentViewModel(IPersonService personService)
{
//GetPerson
Person.PropertyChanged +=person_PropertyChanged;
}
And write an method to handle that event in which you call either CommandManager.InvalidateRequerySuggested() or SaveCommand.RaiseCanExecuteChanged()
private void person_PropertyChanged(object sender, EventArgs args)
{
CommandManager.InvalidateRequerySuggested();
//SaveCommand.RaiseCanExecuteChanged()
}
Hope this works. :-)
try this with all the properties that can change error:
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
OnPropertyChanged("Error");
}
}
Alternatively
switch (columnName)
{
// logic here to validate columns...
OnPropertyChanged("Error");
}
The problem you are having is that the OnPropertyChanged is not being called when the error changes.
The next step is to subscribe to the person's propertychanged event when its created, and create a handler that checks for the propertychanged and then changes the boolean variable that the command uses.
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
Person.PropertyChanged+= PersonPropertyChangedHandler;
SaveCommand = new DelegateCommand(Save, personHasError);
}
bool personHasError = false;
void PersonPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Error")
{
if(Person.Error == null)
personHasError = true;
else
personHasError = false;
}
}
Hope this works. I built this by hand and didn't check it so let me know if its buggy or whatever and ill correct it
In the nutshell - you should call yourDelegateCommand.RaiseCanExecuteChanged() when you think that your CanExecute() return value can be changed.
In your example, you should notify through INotifyPropertyChanged interface that your Person.Error property is changed, subscribes to Person.PropertyChanged event in your ContentViewModel class and call SaveCommand.RaiseCanExecuteChanged() each time when your Person.Error is changed. Please be careful - in your scenario Person.Error isn't recalculated automatically when, for example, Person.FirstName is changed - you should do this manually.
UPDATED:
public class ContentViewModel : ViewModelBase, IContentViewModel
{
public ContentViewModel(IPersonService personService)
{
Person = personService.GetPerson();
Person.PropertyChanged += Person_PropertyChanged;
SaveCommand = new DelegateCommand(Save, CanSave);
}
private void PersonPropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
SaveCommand.RaiseCanExecuteChanged();
}
private void Save()
{
// Save actions here...
}
private bool CanSave()
{
return IsErrorPresented(Person);
}
private bool IsErrorPresented(object o)
{
if (!(o is IDataErrorInfo))
return false;
var propNames = o.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(p => p.Name);
var o2 = (o as IDataErrorInfo);
var errors = propNames.Select(p => o2[p])
.Where(p => !String.IsNullOrEmpty(p))
.ToList();
ValidationSummary.ErrorMessages = errors;
return errors.Count > 0;
}
}
<TextBox Text="{Binding Person.FirstName,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
<Button Content="Save" Command="{Binding SaveCommand}" />
If you will also specify PropertyChanged as UpdateSourceTrigger, your save button will be updated during your typing..
I am learning about observable collections, so I wrote a small program to test my progress.
I have a observable collection class that I supply initial values from a list and bind the observablecollection to a Datagrid. It works great, but when I clear the list using myListOfPlayers.myList.Clear(), the Datagrid does not clear. I thought that the INotifyPropertyChanged property would handle that. What am I doing wrong?
public class PlayerList : ObservableCollection<PlayerName> //observable collection class
{
public PlayerList()
: base()
{
Clear();
foreach (var p in myListOfPlayers.myList.OrderBy(x => x.Score))
{
Add(p);
}
}
public PlayerList(List<PlayerName> list)
: base(list)
{
Clear();
foreach (var p in list)
{
Add(p);
}
}
}
I implement INotifyPropertyChanged in the PlayerName class:
public class PlayerName : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private int _score;
public int Score
{
get { return _score; }
set
{
_score = value;
OnPropertyChanged("Score");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If you clear myListOfPlayers.myList, the PlayerList is not supposed to be cleared... There is no relation between these 2 lists: you just used myListOfPlayers.myList to initialize the content of PlayerList, but they are two different lists. What you do on one of them doesn't affect the other