I want to set the Visbility of an Expander based on the selected value of a ComboBox.
That ComboBox is already mapped to an object from the Model:
<ComboBox Name="SelectedCar" ItemsSource="{Binding Path=CarCategories}" SelectedValue="{Binding Path=Car.CarCategory, Mode=TwoWay}"/>
I've set a property in the VM that derives the Visbility this way:
private Visibility _extraCarDetailsVisibility;
public Visibility ExtraCarDetailsVisibility
{
get
{
if (ManagedPortfolioSelected != null)
{
var category = Car.CarCategory.ToLower();
if (category == "porsche")
{
_extraCarDetailsVisibility= Visibility.Visible;
}
}
_extraCarDetailsVisibility= Visibility.Collapsed;
return _extraCarDetailsVisibility;
}
set
{
_extraCarDetailsVisibility= value;
NotifyPropertyChanged("ExtraCarDetailsVisibility");
}
}
And this is how I use it:
<Expander Visibility="{Binding Path=ExtraCarDetailsVisibility}">
However this doesn't work since I think the CarCategories change event isn't subscribed (and it seems I cannot really as it comes from the Model) so the ExtraCarDetailsVisibility property is never recalled when I cahnge the Car Category...
How would you do this? Thank you!
There was couple of issues there:
I was missing to inherit from the INotifyPropertyChanged in my ICar interface although implemented in the Car object - so couln't reach the PropertyChanged event there.
I had to update the code in order to fire an event each time my ComboBox Catgory was changed. To do so, I hadto:
Hook some event to the PropertyChanged event of the Car object:
private ICar _carSelected;
public IPortfolio CarSelected
{
get { return _carSelected; }
set
{
if (_carSelected!= value)
{
if (_carSelected!= null)
{
_carSelected.PropertyChanged -= OnCarSelectedPropertyChanged;
}
_carSelected= value;
// Also subscribing to any change that happens to the Carselected
_carSelected.PropertyChanged += OnCarSelectedPropertyChanged;
}
// Notifying when the selected portfolio is changed
NotifyPropertyChanged("CarSelected");
}
}
Trigger the event once the CarSelected property changes:
void OnCarSelectedPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CarCategory")
{
NotifyPropertyChanged("ExtraCarDetailsVisibility");
}
}
... that will then update my visibility property I've hooked to the View:
private Visibility _extraCarDetailsVisibility;
public Visibility ExtraCarDetailsVisibility
{
get
{
_extraCarDetailsVisibility= Visibility.Collapsed;
if (PendingCarSelected != null)
{
var category = CarSelected.CounterpartyCategory.ToLower();
if (category == "porsche" || category == "porsches")
{
_extraCarDetailsVisibility= Visibility.Visible;
}
}
return _extraCarDetailsVisibility;
}
set
{
_extraCarDetailsVisibility= value;
NotifyPropertyChanged("ExtraCarDetailsVisibility");
}
}
Related
Following MVVM architecture, we have a view with 2 DataGrids whose data are related and view-model having ObservableCollection<Model>.
Model have boolean property, based on which is one of the grid enabled/disabled. Everything works well and when I change the selected Model, the grid becames unusable. However, when I change the property, the Notify of the customer property IsSelectedModelChecked is not invoked (When I check/uncheck the checkbox in 1st grid, I need to invoke Notify over the property IsSelectedModelChecked).
Q: How am I able to invoke property change on Model's property change?
I've checked several questions, but none of them answered mine. I'd like to provide some ideas from top of my head, but I don't have a clue. Thanks
<unnecessary code ommited>
XAML:
<DataGrid
ItemsSource="{Binding Models}"
SelectedItem="{Binding SelectedModel}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"/>
<DataGridCheckBoxColumn Header="Check"
Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}">
</DataGrid.Columns>
</DataGrid>
<DataGrid
IsEnabled="{Binding IsSelectedModelChecked}"/>
Model:
public class Model : Microsoft.VisualStudio.PlatformUI.ObservableObject
{
private string mName = #"<Name>";
public string Name
{
get { return mName; }
set { SetProperty(ref mName, value); }
}
private bool mIsChecked = false;
public bool IsChecked
{
get { return mIsChecked; }
set { SetProperty(ref mIsChecked, value); }
}
}
View-Model:
public class ViewModel : Microsoft.VisualStudio.PlatformUI.ObservableObject
{
private Model mSelectedModel;
public Model SelectedModel
{
get { return mSelectedModel; }
set
{
SetProperty(ref mSelectedModel, value);
NotifyPropertyChanged(nameof(IsSelectedModelChecked));
}
}
private ObservableCollection<Model> mModels = new ObservableCollection<Model>();
public ObservableCollection<Model> Models
{
get { return mModels; }
set { SetProperty(ref mModels, value); }
}
public bool IsSelectedModelChecked => SelectedModel?.IsChecked ?? false;
}
You have to listen for your SelectedModel's PropertyChanged event.
I.e.
public Model SelectedModel
{
get { return mSelectedModel; }
set
{
if (mSelectedModel != null)
mSelectedModel.PropertyChanged -= OnSelectedModelPropertyChanged;
SetProperty(ref mSelectedModel, value);
if (mSelectedModel != null)
mSelectedModel.PropertyChanged += OnSelectedModelPropertyChanged;
NotifyPropertyChanged(nameof(IsSelectedModelChecked));
}
}
private void OnSelectedModelPropertyChanged( object sender, PropertyChangedEventArgs args )
{
if (args.PropertyName == nameof(Model.IsChecked))
NotifyPropertyChanged(nameof(IsSelectedModelChecked));
}
Also, you will want to take care about leaking the view model instance if your model instance lives longer than the view model.
This is an extension to Haukinger's answer. He mentions issue with memory leak, that can occur when the ViewModel instance will be destroyed, but the model will exist and the handler will be still attached to the events.
In such case, WPF has implemented something called Weak Event Pattern, which allows to handle such situation. For the property change, there is existing PropertyChangedEventManager that implements such Weak Event Pattern. The code should be written as:
public Model SelectedModel
{
get { return mSelectedModel; }
set
{
if (mSelectedModel != null)
PropertyChangedEventManager.RemoveHandler(mSelectedModel, OnSelectedModelPropertyChanged, "");
SetProperty(ref mSelectedModel, value);
if (mSelectedModel != null)
PropertyChangedEventManager.AddHandler(mSelectedModel, OnSelectedModelPropertyChanged, "");
NotifyPropertyChanged(nameof(IsSelectedModelChecked));
}
}
private void OnSelectedModelPropertyChanged( object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == nameof(Model.IsChecked))
NotifyPropertyChanged(nameof(IsSelectedModelChecked));
}
I have checkbox in my view which has bound to the property in the viewmodel. When I check/uncheck the checkbox, there is one condition in setter of the property which updates the same property if the condition is true. But when the property gets updated corresponding view does not change.
Here is the code:
View:
<CheckBox IsChecked="{Binding HoldingPen,Mode="Twoway" ,UpdateSourceTrigger=PropertyChanged}"/>
ViewModel:
public bool HoldingPen
{
get{m_holdingPen;}
set
{
m_hodingPen=value;
onPropertyChanged("HoldingPen");
OnHoldingPenCheckChanged();
}
public void OnHoldingPenCheckChanged()
{
if(HoldingPen && some other condition)
{
HoldingPen=false; //Here view should be updated simultaneously
}
}
I think it's a result of having two onPropertyChanged events fire, once with a value of true and one with a value of false
Typically for this kind of logic I prefer to use the PropertyChanged event instead of hiding the logic in property setters.
public class MyClass()
{
public MyClass()
{
// attach property changed in constructor
this.PropertyChanged += MyClass_PropertyChanged;
}
private void MyClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "HoldingPen")
OnHoldingPenCheckChanged();
}
public bool HoldingPen
{
get{ m_holdingPen; }
set
{
if (m_hodingPen == value)
return;
m_hodingPen=value;
onPropertyChanged("HoldingPen");
}
}
public void OnHoldingPenCheckChanged()
{
if(HoldingPen && some other condition)
{
HoldingPen=false; //Here view should be updated simultaneously
}
}
}
This has the additional benefit of having any custom code to modify a value in one location, rather than going through each setter when looking for something.
ViewModel:
public FloatingToolbarWindowViewModel(GuiItems guiItems)
{
GuiItemsInstance = guiItems;
GuiItemsInstance.Host = Host;
GuiItemsInstance.RefreshVisibility = RefreshVisibility;
}
private Visibility _windowVisibility;
public Visibility WindowVisibility
{
get { return _windowVisibility; }
set
{
//raises PropertyChanged-event
SetValue(ref _windowVisibility, value);
}
}
// this check if any of the toolbars should be in a window and then sets visibility
public void RefreshVisibility(int RoleId)
{
if (GuiItemsInstance.ToolbarItems.Any(i => i.ToolbarLocation == ToolbarLocation.Float && i.RoleId == RoleId))
WindowVisibility = Visibility.Visible;
else
WindowVisibility = Visibility.Hidden;
}
XAML:
Visibility="{Binding WindowVisibility, Mode=TwoWay}"
This means it can never work because in the end the ShowWindow updates the property to Visible even though the initialization would have "decided" it should be Hidden.
So what I do is a hack in code behind file:
public partial class FloatingToolbarWindow : Window
{
public FloatingToolbarWindow()
{
InitializeComponent();
ContentRendered += FloatingToolbarWindow_ContentRendered;
}
private void FloatingToolbarWindow_ContentRendered(object sender, EventArgs e)
{
((FloatingToolbarWindowViewModel)DataContext).RefreshWindowVisibility();
}
ViewModel extra Hack-method:
public void RefreshVisibility()
{
RefreshVisibility(GuiItemsInstance.ActiveRoleId);
}
Is there a way to do this without this terrible hack. Moreover shouldn't this work with Mode=OneWay binding in the 1st place?
I'd make WindowVisibility a readonly property:
public Visibility WindowVisibility
{
get
{
if (GuiItemsInstance.ToolbarItems.Any(i => i.ToolbarLocation == ToolbarLocation.Float && i.RoleId == RoleId))
return Visibility.Visible;
else
return Visibility.Hidden;
}
}
bind the visibility OneWay:
Visibility="{Binding WindowVisibility, Mode=OneWay}"
and then whenever you have to "update" the visibility just raise the property changed... I don't know what you are using, could either
OnPropertyChanged("WindowVisibility");
or
OnPropertyChanged(() => WindowVisibility);
My issue seems to be "scope", though I'm not certain that's the right terminology. I want to notify a read-only list to re-evaluate itself when a property within a custom object is set. I believe it is simply not aware of it's existence. Maybe there is an easy way around this I cannot think of, but I'm drawing a blank.
I find this hard to put into words, so here's simplified code with my comments on what I expect to happen.
Properties within object in which I am databinding to:
private CvarAspectRatios _aspectRatio = new CvarAspectRatios("none", GetRatio());
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{ // This setter never gets hit since I bind to this
if (value != null) // object's 'Value' property now.
{
_aspectRatio = value;
NotifyPropertyChanged("AspectRatio");
NotifyPropertyChanged("ResolutionList"); // I want to inform ResolutionList
} // that it needs to repopulate based
} // on this property: AspectRatio
}
private ResolutionCollection _resolutionList = ResolutionCollection.GetResolutionCollection();
public ResolutionCollection ResolutionList
{
get
{
ResolutionCollection list = new ResolutionCollection();
if (AspectRatio != null && AspectRatio.Value != null)
{
foreach (Resolutions res in _resolutionList.Where(i => i.Compatibility == AspectRatio.Value.Compatibility))
{
list.Add(res);
}
return list;
}
return _resolutionList;
}
}
CvarAspectRatios Class:
public class CVarAspectRatios : INotifyPropertyChanged
{
private string _defaultValue;
public string DefaultValue
{
get { return _defaultValue; }
set { _defaultValue = value; NotifyPropertyChanged("DefaultValue"); }
}
private AspectRatios _value;
public AspectRatios Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged("Value");
NotifyPropertyChanged("ResolutionList"); // This value gets set, and I'd like for ResolutionList to update
} // but it cannot find ResolutionList. No errors or anything. Just
} // no update.
public AspectRatios() { }
public AspectRatios(string defaultValue, AspectRatios val)
{
DefaultValue = defaultValue;
Value = val;
}
// Implementation of INotifyPropertyChanged snipped out here
}
What do you folks think? If you'd like a sample application I can whip one up.
Since CVarAspectRatios implements INotifyPropertyChanged, you can have the viewmodel class subscribe to the PropertyChanged event for the AspectRatio.
public class YourViewModel
{
public YourViewModel()
{
AspectRatio.PropertyChanged += AspectRatio_PropertyChanged;
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
NotifyPropertyChanged("ResolutionList");
}
}
Just bear in mind that if you discard that AspectRatio object (if the object reference changes and not just the value property of that object), you should unsubscribe from the event on the discarded one.
To just transform your existing code into something which should work:
private CvarAspectRatios _aspectRatio; //No field initialization because that would not attach event handler, you could do it though and take care of the handler alone in the ctor
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{
if (_aspectRatio != value) // WTH # "value != null"
{
_aspectRatio.PropertyChanged -= AspectRatio_PropertyChanged;
_aspectRatio = value;
_aspectRatio.PropertyChanged += new PropertyChangedEventHandler(AspectRatio_PropertyChanged);
NotifyPropertyChanged("AspectRatio");
}
}
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
NotifyPropertyChanged("ResolutionList");
}
}
Why don't you factor out re-populating ResolutionList into a separate private method which gets called from the setter of AspectRatios?
If a list needs to update based on a changed property, the list (or a list manager object, for better encapsulation) would normally need to subscribe to the PropertyChanged event of the object hosting the property. If the list is itself a property of the same object, as in this case, it would be simpler and leaner for the property's setter to call a method that updates the list.
I have an ObservableCollection binded to a listbox
public ObservableCollection<InsertionVM> Insertions
{
get
{
return _insertions;
}
set
{
_insertions = value;
base.OnPropertyChanged("ChromosomeList");
}
}
Its member, InsertionVM implements INotifyPropertyChanged. It has a property that will be updated.
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
base.OnPropertyChanged("IsChecked");
}
}
Why doesn't the ObservableCollection refresh even though I implement the INotifyPropertyChanged interface for each property?
Update:
I tried the link given below, but the "more sensitive collection" is only updated when objects are removed / added.
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (InsertionVM item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (InsertionVM item in e.NewItems)
{
//Added items
item.PropertyChanged += EntityViewModelPropertyChanged;
}
}
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Debugger does not reach here
}
Constructor:
public ChromosomeVM(Chromosome _chr, string insertionFilePath)
{
Chr = _chr;
_insertions.CollectionChanged += ContentCollectionChanged;
}
This is your code: (please see the comment also, made by me)
public ObservableCollection<InsertionVM> Insertions // propertyName == Insertions
{
get
{
return _insertions;
}
set
{
_insertions = value;
base.OnPropertyChanged("ChromosomeList"); // What is ChromosomeList??
}
}
Can you see the problem now? Change ChromosomeList to Insertions. Hope some problem at least will be fixed!
Always remember the following:
ObservableCollection<T> only notifies when number of items (it may stay same, when one item is added and one is removed, but you get the point) in it changes.
If an item in ObservableCollection<T> changes, collection is not responsible for propagating change notifications.
be sure to put the [Insertions] in the path of the binding and it will work
[Insertions] will be updated only if you change the reference like.
Insertions = new ObservableCollection<InsertionVM>( Items);
To make your code more effective add check if the value change in the set like
public ObservableCollection<InsertionVM> Insertions
{
get
{
return _insertions;
}
set
{
if(_insertions != value)
{
_insertions = value;
base.OnPropertyChanged("Insertions");
}
}
}