I am a WPF beginner and I have an issue with the PropertyChanged Events:
I have a viewmodel that contains an instance of another viewmodel. (I will use general names here)
I want the instance of the AxisVM to notify my SomeDouble property. (Can´t use a converter)
edit: I didn´t include the full classes here, the PropertyChangedEvent is obviously implemented.
public class ViewModel : INotifyPropertyChanged
{
private AxisVM axis;
public ViewModel()
{
this.AxisVM = new AxisVM();
}
public AxisVM Axis
{
get { return axis};
set { axis = value; FireOnPropertyChanged(); }
}
public double SomeDouble
{
get { return axis.Lowerlimit * 1.5 };
}
}
AxisVM also inherits from INotifyPropertyChanged (I use the ClassMemberName)
public class AxisVM: INotifyPropertyChanged
{
private double lowerLimit;
public double LowerLimit
{
get { return lowerLimit };
set { lowerLimit = value; FireOnPropertyChanged(); }
}
}
In XAML I bind the Viewmodel as a DataContext (where doesn´t matter in this case I think) and then bind the Lower limit to a textbox.
When I edit the textbox, the event of the lower limit of the axis gets fired and the value is changed (view and viewmodel) but I need to notify my SomeDouble property because it gets updated when the lower limit changes.
The property changed event of the axis instance in my ViewModel never gets fired even though I access a property of it (which does fire its event but doesn´t notify my SomeDouble property).
I am at loss right now, any help is appreciated.
Just handle the PropertyChanged of the AxisVM in your view model:
public class ViewModel : INotifyPropertyChanged
{
private readonly AxisVM axis;
public ViewModel()
{
axis = new AxisVM();
axis.PropertyChanged += Axis_PropertyChanged;
}
private void Axis_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
FireOnPropertyChanged(nameof(SomeDouble));
}
...
}
You could use an event for this.
Add an event in your AxisVM
public class AxisVM: INotifyPropertyChanged
{
public event EventHandler LowerLimitChanged;
private double lowerLimit;
public double LowerLimit
{
get { return lowerLimit };
set { lowerLimit = value; FireOnPropertyChanged(); LowerLimitChanged?.Invoke(this, EventArgs.Empty); }
}
}
And subscribe like this
public class ViewModel : INotifyPropertyChanged
{
private AxisVM axis;
public ViewModel()
{
this.AxisVM = new AxisVM();
this.AxisVM.LowerLimitChanged += OnLowerLimitChanged;
}
public AxisVM Axis
{
get { return axis};
set { axis = value; FireOnPropertyChanged(); }
}
public double SomeDouble
{
get { return axis.Lowerlimit * 1.5 };
}
public void OnLowerLimitChanged(object sender, EventArgs e)
{
FireOnPropertyChanged("SomeDouble");
}
}
You can remove FireOnPropertyChanged("SomeDouble"); in your property public AxisVM Axis because this will only be fired when the instance of AxisVM is set and not when a property in this instance has changed.
When you implemented INotifyPropertyChanged in AxisVM, you added PropertyChanged event there. Handle it in ViewModel and fire FireOnPropertyChanged().
Axis.PropertyChanged += OnAxisVMPropertyChanged(...)
void OnAxisVMPropertyChanged(..)
{
// Check property name
// Fire OnPropertyChanged for LowerLimit
}
Related
I have created various properties inside of a User Control, and have had great success with accessing and editing them. I'm now trying to set up events for a number of these to be raised when one of these properties is changed. I have tried the MSDN example code for doing this (see here), but it is giving me this error when I try to build the solution:
Severity Code Description Project File Line Suppression State
Error CS0079 The event 'AbilityScoreDisplay.AbilityTitleChanged' can only appear on the left hand side of += or -= DnD Character Sheet C:\Users\bradley beasley\Documents\Visual Studio 2019\Projects\DnD Character Sheet\DnD Character Sheet\AbilityScoreDisplay.Designer.cs 199 Active
Another issue that I am having is that I am struggling to figure out how to get that event to appear in the Visual Studio 2019 Designer Properties window.
Here is the code that I have added to the designer file:
namespace DnD_Character_Sheet
{
partial class AbilityScoreDisplay : UserControl
{
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
Invalidate();
}
}
public int AbilityModifier
{
get
{
return Convert.ToInt32(AbilityModifierTextBox.Text);
}
private set
{
if (value >= 0) AbilityModifierTextBox.Text = String.Format("+{0}", value);
else AbilityModifierTextBox.Text = value.ToString();
Invalidate();
}
}
public int AbilityScore
{
get
{
return Convert.ToInt32(AbilityScoreLabel.Text);
}
set
{
AbilityModifier = (int)(Math.Floor((double)(value) / 2)) - 5;
Invalidate();
}
}
private EventHandler onAbilityTitleChanged { get; set; }
private EventHandler onAbilityScoreChanged { get; set; }
public event EventHandler AbilityTitleChanged
{
add
{
onAbilityTitleChanged += value;
}
remove
{
onAbilityTitleChanged -= value;
}
}
public event EventHandler AbilityScoreChanged
{
add
{
onAbilityScoreChanged += value;
}
remove
{
onAbilityScoreChanged -= value;
}
}
protected virtual void OnAbilityTitleChanged(EventArgs e)
{
AbilityTitleChanged?.Invoke(this, e);
}
protected virtual void OnAbilityScoreChanged(EventArgs e)
{
AbilityScoreChanged?.Invoke(this, e);
}
}
}
The aim is to enable an event to be raised whenever a property is changed so that it can do other stuff elsewhere in the form that the controls will be in. I'm fairly certain that I am missing some very important stuff, or that my code is not that effective at all, but I am learning this kind of code for the first time, and I have tried many different things that have just not worked.
Any help at all would be greatly appreciated :)
I think you are confusing a few concepts. Let's do it step by step.
First, you need to be able to track event handlers:
private EventHandler _onAbilityTitleChanged;
You expose this event through a public property:
public event EventHandler AbilityTitleChanged
{
add
{
_onAbilityTitleChanged += value;
}
remove
{
_onAbilityTitleChanged -= value;
}
}
Finally, you need to fire the event so that all subscribed handlers can react to it. You can do so when the title changes (setter):
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
//Raising the event!
_onAbilityTitleChanged?.Invoke(this, new EventArgs());
}
}
Other classes can then subscribe to your event:
var control = new AbilityScoreDisplay();
control.AbilityTitleChanged += SomeHandlerForWhenTitleChanges;
private void SomeHandlerForWhenTitleChanges(object sender, EventArgs e)
{
//....
}
You might want to read up a bit on the INotifyPropertyChanged interface as well.
You typically do this by implementing INotifyPropertyChanged. This allows you to use one single event for all the properties. The property name is passed in the event arguments.
partial class AbilityScoreDisplay : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
}
In the properties do this (with AbilityModifier as an example):
private int _abilityModifier;
public int AbilityModifier
{
get { return _abilityModifier; }
private set {
if (value != _abilityModifier) {
_abilityModifier = value;
AbilityModifierTextBox.Text = value >= 0
? String.Format("+{0}", value)
: value.ToString();
OnPropertyChanged(nameof(AbilityModifier));
}
}
}
Assuming this event handler
private void ScoreDisplay_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
You can subscribe the event with
PropertyChanged += ScoreDisplay_PropertyChanged;
You need to use the add/remove syntax only in rare cases. Typically, when you create your own event store, because you have a lot of events and don't want to consume space for unsubscribed events.
You can use INotifyPropertyChanged together with data binding to immediately update the UI when changes are made to the data. To do this you would create a class with properties and the INotifyPropertyChanged implementation. In the form you then assign an instance of this class to the DataSource of a BindingSource. The controls are then bound to this BindingSource.
Then you can drop all the code used to read from or to write to text boxes or labels etc., as the binding mechanism does it automatically for you.
I use custom events on my graphic objects to notify of object's changes :
public class OnLabelWidthChangedEventArgs : EventArgs
{
private float _width;
public float Width
{
get { return _width; }
set { _width = value; }
}
public OnLabelWidthChangedEventArgs(float widthParam) : base()
{
Width = widthParam;
}
}
This is the object firing this event :
public class DisplayLabel : DisplayTextObject
{
public event EventHandler<OnLabelWidthChangedEventArgs > OnLabelSizeChanged;
public DisplayLabel(ScreenView _screenParam, IXapGraphicObject obj) : base(_screenParam, obj)
{
l = new Label();
SetSize();
}
public override void SetSize()
{
Width = w;
Height = h;
if(OnLabelWidthChanged != null)
OnLabelSizeChanged.Invoke(this, new OnLabelWidthChangedEventArgs(w)); // OnLabelSizeChanged is null
}
The OnLabelSizeChanged is always null, how can I initialise it.
I have a working solution with delegates instead of custom events:
public event OnWidthChanged WidthChanged = delegate { };
but I'd like to know how to solve this issue with custom events.
Thank you for your help.
You don't initialize your event, you assign a handler to it (aka. subscribing to it), something similar to this:
myDisplayLabel.OnLabelWidthChanged += MyEventHandlerMethod;
where MyEventHandlerMethod is a method matching the event's signature, i.e.
void MyEventHandlerMethod(Object sender, OnLabelWidthChangedEventArgs)
Bedtime reading: https://msdn.microsoft.com/en-us/library/9aackb16(v=vs.110).aspx
I have a Windows 8.1 application with a parent and child viewmodel in the following relationship
ParentViewModel
class ParentViewModel {
private double _parentAmount;
public double parentAmount
{
get { return _parentAmount; }
set
{
if (value != _parentAmount)
{
_parentAmount = value;
NotifyPropertyChanged("parentAmount");
}
}
}
private ObservableCollection<ChildViewModel> _children;
public ObservableCollection<ChildViewModel> children
{
get { return _children; }
set
{
if (value != _children)
{
_children = value;
NotifyPropertyChanged("children");
}
}
}
}
ChildViewModel
class ChildViewModel {
private double _ChildAmount;
public double ChildAmount
{
get { return _ChildAmount; }
set
{
if (value != _ChildAmount)
{
_ChildAmount = value;
NotifyPropertyChanged("ChildAmount");
}
}
}
}
In the XAML there is TextBlock that is bound to the "ParentAmount" and then there is a ListView bound to the Observable collection "Children". ListView's Itemtemplate is a datatemplate with a TextBox with a two way bind to the "ChildAmount". The user can modify the value in the child TextBox
Now my requriement is to update the ParentAmount with the sum of all its child amount on the fly when the user modifies one of the child amounts. How do I achieve this?
For illustration purpose I have simplified the code example pasted above, the ChildViewModel has more functionality than what can be seen hence I can't replace that ObservableCollection of ChildViewModel with a List of double for instance.
I would be very glad if someone can point me in the right direction. Thanks in Advance.
With a very small addition, this will do the trick.
The specific changes are adding a property change handler for each child object in the ObservableCollection.
Note that this is a crude example to set you on the right track - I haven't unhooked the event handlers, and I recalculate the parent amount on any change from the child (i.e. I don't check that it was the ChildAmount that changed, this means you end up with more action than is necessary). I also haven't put in any code to handle changes to the contents of the ObservableCollection so if new items are added to it they won't have a property change event handler attached - this is simple for you to do yourself.
Note my use of a BaseViewModel - this is just good practice, it saves you from reimplementing the INotifyPropertyChanged interface on every class that needs it.
class ParentViewModel : BaseViewModel
{
private double _parentAmount;
public double parentAmount
{
get { return _parentAmount; }
set
{
if (value != _parentAmount)
{
_parentAmount = value;
NotifyPropertyChanged("parentAmount");
}
}
}
private ObservableCollection<ChildViewModel> _children;
public ObservableCollection<ChildViewModel> children
{
get { return _children; }
set
{
if (value != _children)
{
_children = value;
foreach (var child in _children)
child.PropertyChanged += ChildOnPropertyChanged;
NotifyPropertyChanged("children");
}
}
}
private void ChildOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
parentAmount = children.Sum(p => p.ChildAmount);
}
}
class ChildViewModel : BaseViewModel
{
private double _ChildAmount;
public double ChildAmount
{
get { return _ChildAmount; }
set
{
if (value != _ChildAmount)
{
_ChildAmount = value;
NotifyPropertyChanged("ChildAmount");
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
protected void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
A more elegant way is to use Reactive Extensions.
First you need to grab
Rx-Main
from Package Manager Console.
Then, create a static class to host your extension method implemented using Rx. Something like this -
public static class Extensions
{
public static IObservable<T> OnPropertyChanges<T>(this T source, string propertyName)
where T : INotifyPropertyChanged
{
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
handler => handler.Invoke,
h => source.PropertyChanged += h,
h => source.PropertyChanged -= h)
.Where(p => p.EventArgs.PropertyName == propertyName)
.Select(_ => source);
}
}
Lastly, you call this method in your ParentViewModel's constructor (or anywhere necessary).
// whenever the ChildAmount property of any ChildViewModel has changed, do something
Observable.Merge(children.Select(c => c.OnPropertyChanges("ChildAmount")))
.Subscribe((c) =>
{
// update your parent amount here
NotifyPropertyChanged("parentAmount");
});
In my WPF MVVM app, using Caliburn.Micro, I have a ViewModel, CreateServiceViewModel that, on a button click, opens a GridView in a seperate window for the User to chose a Row from.
I created another ViewModel for this, MemberSearchViewModel which has two properties:
private Member selectedMember;
public Member SelectedMember
{
get { return selectedMember; }
set { selectedMember = value; }
}
private IList<Member> members;
public IList<Member> Members
{
get { return members; }
set { members = value; }
}
How do I get that SelectedMember value back to the calling ViewModel? That ViewModel has a property of Service.SelectedMember.
EventAggregator is what you could use... One of many solutions I am sure.
public class MessageNotifier{
public object Content{get;set;}
public string Message {get;set;}
}
//MEF bits here
public class HelloWorldViewModel: Screen, IHandle<MessageNotifier>{
private readonly IEventAggregator _eventAggregator
//MEF constructor bits
public YourViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public override OnActivate(){
_eventAggregator.Subscribe(this);
}
public override OnDeactivate(){
_eventAggregator.UnSubscribe(this);
}
//I Handle all messages with this signature and if the message applies to me do something
//
public void Handle(MesssageNotifier _notifier){
if(_notifier.Message == "NewSelectedItem"){
//do something with the content of the selectedItem
var x = _notifier.Content
}
}
}
//MEF attrs
public class HelloWorld2ViewModel: Screen{
private readonly IEventAggregator _eventAggregator
//MEF attrs
public HelloWorld2ViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public someobject SelectedItem{
get{ return _someobject ;}
set{ _someobject = value;
NotifyOfPropertyChange(()=>SelectedItem);
_eventAggregator.Publish(new MessageNotifier(){ Content = SelectedItem, Message="NewSelectedItem"});
}
}
One option is to utilize NotifyPropertyChanged. Since you are working with ViewModels, they most likely implement INotifyPropertyChanged, which you can make use of just as the framework does.
When your CreateServiceViewModel creates the MemberSearchViewModel, it would just subscribe to the PropertyChanged event:
//This goes wherever you create your child view model
var memberSearchViewModel = new MemberSearchViewModel(); //Or using a service locator, if applicable
memberSearchViewModel.PropertyChanged += OnMemberSearchPropertyChanged;
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
}
}
And then in your MemberSearchViewModel, you simply raise the NotifyPropertyChanged event when the user has selected a member from the grid.
EDIT:
As #DNH correctly notes in the comments, using event handlers like this can lead to memory leaks if not properly cleaned up. So when you are finished with the MemberSearchViewModel, make sure to unsubscribe to the PropertyChanged event. So for example, if you only need it until the user selects a member, you could put it inside the Property Changed Handler itself (I've switched it to use a class-level variable to hold the ViewModel):
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
//Unsubscribe so the view model can be garbage collected
_memberSearchViewModel.PropertyChanged -= OnMemberSearchPropertyChanged;
_memberSearchViewModel = null;
}
}
One option would be to store MemberSearchViewModel as a field of CreateServiceViewModel and define CreateServiceViewModel.SelectedMember property as follows:
public Member SelectedMember
{
get
{
return _memberSearchViewModel.SelectedMember;
}
set
{
_memberSearchViewModel.SelectedMember = value;
}
}
How about?
public interface INotifyMe<T>
{
T ResultToNotify { get; set; }
}
public class CreateServiceViewModel : ViewModelBase, INotifyMe<Member>
{
// implement the interface as you like...
}
public class MemberSearchViewModel : ViewModelBase
{
public MemberSearchViewModel(INotifyMe<Member> toBeNotified)
{
// initialize field and so on...
}
}
Now you could let listen CreateServiceViewModel to its own property and you won't have to think about the removal of the event listener.
Well of course to do the more classical way you could alternatively use an interface like this.
public interface INotifyMe<T>
{
void Notify(T result);
}
As a follow-up to my comment, here's an example using Prism - I've never used Caliburn.
Create an event - the event's payload will be your SelectedMember:
public class YourEvent:CompositePresentationEvent<YourEventPayload>{}
Publish the event:
EventAggregator.GetEvent<YourEvent>().Publish(YourEventPayload);
Subscribe to the event:
EventAggregator.GetEvent<YourEvent>().Subscribe((i) => ...);
There is a UIHealthBar which is binded to a viewmodel property that is changed from 5 to 10. I would like to animate it with filled color from a old value (5) to new value (10). How can I do it in mvvmcross with a better approach ?
This sounds like it could be done with a pair of viewmodel properties - perhaps a tuple that is always changed together - e.g.
public class MyViewModel : MvxViewModel
{
public MyViewModel()
{
// subscribe for health updates here
}
public class HealthTuple
{
public double Old {get;set;}
public double New {get;set;}
}
private HealthTuple _health;
public HealthTuple Health
{
get { return _health; }
set { _health = value; RaisePropertyChanged(() => Health); }
}
private void OnNewHealth(HealthMessage message)
{
Health = new HealthTuple() { Old = _health.New, New = message.Value };
}
}
Your custom UIView - the UIHealthBar can then expose either a single property or two properties and you can bind these to the ViewModel's Health values. Drawing/animating the display is then 'normal UI kit work'