I have a strange situation:
in a .NET CF project there is a class (call it A) which has the following structure:
public partial class A: Form, INotifyPropertyChanged
{
//for simplicity stripping off everything unrelated to this problem
private int _SelectedRowsCount = 0;
public int SelectedRowsCount
{
get { return _SelectedRowsCount; }
set
{
_SelectedRowsCount = value;
OnPropertyChanged("SelectedRowsCount");
}
}
public bool enableCollectionButton
{
get { return SelectedRowsCount > 0; }
}
//....
//
//
void SomeMethod()
{
//for simplicity:
SelectedRowsCount = 1; //<- HERE NOT FIRING Propertychanged for enableCollectionButton
}
}
The class implements correctly the INotifyPropertyChanged interface which makes the the SelectedRowsCount property to fire a property changed notification (i evaluated this with the debugger).
The enableCollectionButton property is databound to some control like so:
someButton.DataBindings.Add("Enabled", this, "enableCollectionButton");
But the enableCollectionButton property does not change (though depending on the value of SelectedRowsCount). This property should be evaluated on a change of the SelectedRowsCount property, BUT IS NOT!!!
Why is this not functioning, what do i miss??
Thanks in advance
Try this
public partial class A: Form, INotifyPropertyChanged
{
//for simplicity stripping off everything unrelated to this problem
private int _SelectedRowsCount = 0;
public int SelectedRowsCount
{
get { return _SelectedRowsCount; }
set
{
_SelectedRowsCount = value;
OnPropertyChanged("SelectedRowsCount");
OnPropertyChanged("enableCollectionButton"); //This changes too !
}
}
public bool enableCollectionButton
{
get { return SelectedRowsCount > 0; }
}
}
What happens is that you're binding to the enableCollectionButton property, but you're not notifying the BindingManager of the change to enableCollectionButton, rather of the change to SelectedRowsCount. The BindingManager doesn't know they're related!
Also try using Microsoft's naming conventions, enableCollectionButton should be EnableCollectionButton
Related
I have some trouble with the data binding while using MVVM in Xamarin. Let me explain my architecture:
I have a Manager-class, which contains a ObservableCollection with classes of type t, t is my model class. The Manager-class contains also a attribute called activeT, which is the current selected object of my model class. There are 2 UIs, one which shows the current data of t. The viewModel is bound to the attribute t of my Manager-class like that:
public t CurrentT
{
get
{
return _mgr.CurrentT;
}
set
{
_mgr.CurrentT = value;
OnPropertyChanged();
}
}
_mgr is my singleton Manager-Object.
Now there is the other view, which is able to choose the current t out of a combobox. The viewModel of that view is bound to the ObservableCollection of the manager. If I change the selected object, I do it like with the same code like above. The Property of the manager is the following code:
public t CurrentT
{
get
{
return _currentT;
}
set
{
_currentT= value;
OnPropertyChanged());
}
}
The problem is now, that the first view to view the current selected t does not refresh, though I can see in the debugger, that the current t is changed by the other view.
Can someone help me?
Edit:
I provide some more Code:
The Manager-Class:
public class Manager : INotifyPropertyChanged
{
private t _currentConstructionSite;
private ObservableCollection<t> _constructionSites = null;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public t CurrentConstructionSite
{
get
{
return _currentConstructionSite;
}
set
{
_currentConstructionSite = value;
OnPropertyChanged("CurrentConstructionSite");
}
}
public ObservableCollection<t> ConstructionSites
{
get
{
return _constructionSites;
}
set
{
_constructionSites = value;
}
}
private Manager()
{
ConstructionSites = DataRepository.GenConstructionSites();
_currentConstructionSite = ConstructionSites[0];
}
}
The ViewModels Class A (This is the viewmodel of the view, which shows some data):
public class DashboardViewModel : ViewModelBase
{
private Manager _mgr;
public t CurrentConstructionSite
{
get
{
return _mgr.CurrentConstructionSite;
}
set
{
_mgr.CurrentConstructionSite = value;
OnPropertyChanged();
}
}
public DashboardViewModel()
{
_mgr = Manager.getInstance();
}
}
The View A to show some data:
Binding Setup from XAML:
<ContentPage.BindingContext>
<local:DashboardViewModel x:Name="viewModel"/>
</ContentPage.BindingContext>
Binding a Label to show data:
<Label Text="{Binding CurrentConstructionSite.ConstructionSiteName, Mode=TwoWay}" HorizontalOptions="Center" Font="Bold" FontSize="Large"/>
ViewModel B to choose the current t:
public class ChooseConstructionSiteViewModel : ViewModelBase
{
Manager _mgr = null;
public ObservableCollection<t> ConstructionSites
{
get
{
return _mgr.ConstructionSites;
}
}
public t CurrentConstructionSite
{
get
{
return _mgr.CurrentConstructionSite;
}
set
{
_mgr.CurrentConstructionSite = value;
OnPropertyChanged();
}
}
public ChooseConstructionSiteViewModel()
{
_mgr = Manager.getInstance();
}
}
The View to choose the current t:
<combobox:SfComboBox x:Name="combobox" Grid.Row="0" Grid.Column="1" Margin="8,0,20,0" VerticalOptions="Center" HeightRequest="40" DataSource="{Binding ConstructionSites}" DisplayMemberPath="ConstructionSiteName" SelectionChanged="Handle_SelectionChanged"/>
And if the selection from the combobox changed:
void Handle_SelectionChanged(object sender, Syncfusion.XForms.ComboBox.SelectionChangedEventArgs e)
{
t selectedItem = e.Value as t;
_viewModel.CurrentConstructionSite = selectedItem;
}
The two views are contained as contetPages in a tabbedPage. It works in general, but the changing the selected t in the view B does not update the data in view A. I can see in the debugger that the value of t is changed via view B but when I go back to view A there is the old value. In the debugger I can see that the value is updated.
BTW: ViewModelBase is the class which implements INotifyPropertyChanged
From you second viewmodel, you need to "notify" the first viewmodel that the data has changed.
One of the ways to do that, would be to use a Messenger (MvvmLight has one, so does MvvmCross). You can then use the messenger from your Manager, to notify all the viewmodels that need the info, that CurrentT changed.
In the messenger subscription in your viewmodels, simply call a RaiseNotifyPropertyChanged of your property, and you should be good to go
Do you know best practices in wpf+mvvm to update Calculated fields?
What I can do instead OnPropertyChanged(nameof(Summary))?
Also calculated field can be in another viewmodel and this viewmodel should not know about all dependences.
This is my code :
public class Model
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
public int Summary => Prop1 + Prop2;
}
public class ViewModel : BaseViewModel
{
public Model Model { get; }
public int Prop1
{
get
{
return Model.Prop1;
}
set
{
Model.Prop1 = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Summary));
}
}
public int Prop2
{
get
{
return Model.Prop2;
}
set
{
Model.Prop2 = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Summary));
}
}
public int Summary => Model.Summary;
}
Calling OnPropertyChanged on the calculated property is perfectly acceptable. If you have a relatively simple model like the one you wrote that'll be enough.
If you have multiple calculated properties on the model, you might consider creating a method to call all of them from a single place, instead of calling each one from every property.
Something like this:
public int Prop1
{
get
{
return _prop1;
}
set
{
_prop1 = value;
OnPropertyChanged();
NotifyCalculatedProperties();
}
}
public int Calc1 { get { /* ... */ } }
public int Calc2 { get { /* ... */ } }
public int Calc3 { get { /* ... */ } }
public void NotifyCalculatedProperties()
{
OnPropertyChanged(nameof(Calc1));
OnPropertyChanged(nameof(Calc2));
OnPropertyChanged(nameof(Calc3));
}
In case the calculated properties exist in a different model, you can register in that Model\VM to the source's PropertyChanged event, and then invoke the change notification there.
Like that:
void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "Prop1")
OnPropertyChanged(nameof(Calc1));
OnPropertyChanged(nameof(Calc2));
OnPropertyChanged(nameof(Calc3));
}
Just don't forget to unsubscribe when the Model\VM changes, or you'll have a memory leak on your hands.
Lastly, you can always use the Messenger to pass messages between unrelated VMs, though you should use caution since it's a very powerful tool, and can easily be misused.
I don't know what MVVM framework you're using, but each has it's own implementation. You can find more general details on the Messenger pattern here: https://msdn.microsoft.com/en-us/magazine/jj694937.aspx
I have
public override bool RelatedProperty
{
get { return this.SomeProperty > 0; }
}
public int SomeProperty
{
get { return this.someProperty; }
protected set
{
this.Set<int>(ref this.someProperty, value);
this.RaisePropertyChanged(nameof(this.RelatedProperty));
}
}
where RelatedProperty is obviously dependent on SomeProperty.
Is there a better way to update the binding, than invoking RaisePropertyChanged for RelatedProperty, from the setter of SomeProperty?
Is there a better way to update the binding, than invoking RaisePropertyChanged for RelatedProperty, from the setter of SomeProperty?
No. At least not using MvvmLight and the imperative approach of implementing properties.
If you were using a reactive UI framework such as ReactiveUI you would handle property changes in a functional way:
public class ReactiveViewModel : ReactiveObject
{
public ReactiveViewModel()
{
this.WhenAnyValue(x => x.SomeProperty).Select(_ => SomeProperty > 0)
.ToProperty(this, x => x.RelatedProperty, out _relatedProperty);
}
private int _someProperty;
public int SomeProperty
{
get { return _someProperty; }
set { this.RaiseAndSetIfChanged(ref _someProperty, value); }
}
private readonly ObservableAsPropertyHelper<bool> _relatedProperty;
public bool RelatedProperty
{
get { return _relatedProperty.Value; }
}
}
You can read more about this in the docs for ReactiveUI and on the creator Paul Betts' blog if you are interested:
https://docs.reactiveui.net/en/fundamentals/functional-reactive-programming.html
http://log.paulbetts.org/creating-viewmodels-with-reactiveobject/
i am looking for a solution to fire PropertyChanged of the whole property, if one of the inner properties changes. so here is the model:
public class MainStatus : ObservableObject
{
private bool _safety;
public bool Safety
{
get { return _safety; }
set
{
if (_safety == value)
return;
_safety = value;
RaisePropertyChanged("Safety");
}
}
private bool _setupMode
public bool SetupMode
{
get { return _setupMode; }
set
{
if (_setupMode == value)
return;
_setupMode = value;
RaisePropertyChanged("SetupMode");
}
}
}
it has some more properties (bool, int and string).
in the vm it is used like this:
private MainStatus _mainStatus;
public MainStatus MainStatus
{
get { return _mainStatus; }
set
{
if (_mainStatus == value)
return;
_mainStatus = value;
RaisePropertyChanged("MainStatus");
}
}
i can bind to the inner properties like this:
<DataTrigger Binding="{Binding MainStatus.Safety}" Value="true">
this all works fine. but i need to send the whole MainStatus to other VMs, if one of the inner property changes. so i changed the property like the this:
public MainStatus MainStatus
{
get { return _mainStatus; }
set
{
if (_mainStatus == value)
return;
_mainStatus = value;
RaisePropertyChanged("MainStatus");
Messenger.Default.Send<MainStatusMessage>(new MainStatusMessage() { MainStatus = _mainStatus });
}
}
but this does not work. it seems like the PropertyChanged for the MainStatus is never fired if one of the inner properties changes. how can i fix that?
In your VM, you could subscribe to Mainstatus.PropertyChanged event something like:
this.MainStatus.PropertyChanged += new PropertyChangedEventHandler(MainStatus_PropertyChanged);
//..........
void MainStatus_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged("MainStatus");
}
If I uderstand the question you want to raise the property changed events for ALL properties on a class that implements INotifyPropertyChanged, this can be achieved by calling the event handler with a NULL instead of the string containing the property name:
e.g. RaisePropertyChanged(null) would raise both Safety and SetUpMode on the MainStatus class defined above.
I'm developing an application in Silverlight2 and trying to follow the Model-View-ViewModel pattern. I am binding the IsEnabled property on some controls to a boolean property on the ViewModel.
I'm running into problems when those properties are derived from other properties. Let's say I have a Save button that I only want to be enabled when it's possible to save (data has been loaded, and we're currently not busy doing stuff in the database).
So I have a couple of properties like this:
private bool m_DatabaseBusy;
public bool DatabaseBusy
{
get { return m_DatabaseBusy; }
set
{
if (m_DatabaseBusy != value)
{
m_DatabaseBusy = value;
OnPropertyChanged("DatabaseBusy");
}
}
}
private bool m_IsLoaded;
public bool IsLoaded
{
get { return m_IsLoaded; }
set
{
if (m_IsLoaded != value)
{
m_IsLoaded = value;
OnPropertyChanged("IsLoaded");
}
}
}
Now what I want to do is this:
public bool CanSave
{
get { return this.IsLoaded && !this.DatabaseBusy; }
}
But note the lack of property-changed notification.
So the question is: What is a clean way of exposing a single boolean property I can bind to, but is calculated instead of being explicitly set and provides notification so the UI can update correctly?
EDIT: Thanks for the help everyone - I got it going and had a go at making a custom attribute. I'm posting the source here in case anyone's interested. I'm sure it could be done in a cleaner way, so if you see any flaws, add a comment or an answer.
Basically what I did was made an interface that defined a list of key-value pairs to hold what properties depended on other properties:
public interface INotifyDependentPropertyChanged
{
// key,value = parent_property_name, child_property_name, where child depends on parent.
List<KeyValuePair<string, string>> DependentPropertyList{get;}
}
I then made the attribute to go on each property:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class NotifyDependsOnAttribute : Attribute
{
public string DependsOn { get; set; }
public NotifyDependsOnAttribute(string dependsOn)
{
this.DependsOn = dependsOn;
}
public static void BuildDependentPropertyList(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
var obj_interface = (obj as INotifyDependentPropertyChanged);
if (obj_interface == null)
{
throw new Exception(string.Format("Type {0} does not implement INotifyDependentPropertyChanged.",obj.GetType().Name));
}
obj_interface.DependentPropertyList.Clear();
// Build the list of dependent properties.
foreach (var property in obj.GetType().GetProperties())
{
// Find all of our attributes (may be multiple).
var attributeArray = (NotifyDependsOnAttribute[])property.GetCustomAttributes(typeof(NotifyDependsOnAttribute), false);
foreach (var attribute in attributeArray)
{
obj_interface.DependentPropertyList.Add(new KeyValuePair<string, string>(attribute.DependsOn, property.Name));
}
}
}
}
The attribute itself only stores a single string. You can define multiple dependencies per property. The guts of the attribute is in the BuildDependentPropertyList static function. You have to call this in the constructor of your class. (Anyone know if there's a way to do this via a class/constructor attribute?) In my case all this is hidden away in a base class, so in the subclasses you just put the attributes on the properties. Then you modify your OnPropertyChanged equivalent to look for any dependencies. Here's my ViewModel base class as an example:
public class ViewModel : INotifyPropertyChanged, INotifyDependentPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
// fire for dependent properties
foreach (var p in this.DependentPropertyList.Where((x) => x.Key.Equals(propertyname)))
{
PropertyChanged(this, new PropertyChangedEventArgs(p.Value));
}
}
}
private List<KeyValuePair<string, string>> m_DependentPropertyList = new List<KeyValuePair<string, string>>();
public List<KeyValuePair<string, string>> DependentPropertyList
{
get { return m_DependentPropertyList; }
}
public ViewModel()
{
NotifyDependsOnAttribute.BuildDependentPropertyList(this);
}
}
Finally, you set the attributes on the affected properties. I like this way because the derived property holds the properties it depends on, rather than the other way around.
[NotifyDependsOn("Session")]
[NotifyDependsOn("DatabaseBusy")]
public bool SaveEnabled
{
get { return !this.Session.IsLocked && !this.DatabaseBusy; }
}
The big caveat here is that it only works when the other properties are members of the current class. In the example above, if this.Session.IsLocked changes, the notification doesnt get through. The way I get around this is to subscribe to this.Session.NotifyPropertyChanged and fire PropertyChanged for "Session". (Yes, this would result in events firing where they didnt need to)
The traditional way to do this is to add an OnPropertyChanged call to each of the properties that might affect your calculated one, like this:
public bool IsLoaded
{
get { return m_IsLoaded; }
set
{
if (m_IsLoaded != value)
{
m_IsLoaded = value;
OnPropertyChanged("IsLoaded");
OnPropertyChanged("CanSave");
}
}
}
This can get a bit messy (if, for example, your calculation in CanSave changes).
One (cleaner? I don't know) way to get around this would be to override OnPropertyChanged and make the call there:
protected override void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "IsLoaded" /* || propertyName == etc */)
{
base.OnPropertyChanged("CanSave");
}
}
You need to add a notification for the CanSave property change everywhere one of the properties it depends changes:
OnPropertyChanged("DatabaseBusy");
OnPropertyChanged("CanSave");
And
OnPropertyChanged("IsEnabled");
OnPropertyChanged("CanSave");
How about this solution?
private bool _previousCanSave;
private void UpdateCanSave()
{
if (CanSave != _previousCanSave)
{
_previousCanSave = CanSave;
OnPropertyChanged("CanSave");
}
}
Then call UpdateCanSave() in the setters of IsLoaded and DatabaseBusy?
If you cannot modify the setters of IsLoaded and DatabaseBusy because they are in different classes, you could try calling UpdateCanSave() in the PropertyChanged event handler for the object defining IsLoaded and DatabaseBusy.