Update View when property value changed inside setter - c#

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.

Related

How can I properly reset a value associated with a ComboBox, from within a PropertyChanged event?

I have a ComboBox that is bound to a property on my ViewModel (from hear on "VM".) When a user makes a selection on the ComboBox it properly updates the bound property in my VM. Within my UI code, I have subscribed to the PropertyChanged event on my VM.
As it should behave, when the user makes a selection within the ComboBox, my PropertyChanged event is correctly executing in my UI back-end code. When the UI code catches the change of this property, under certain selection conditions I need to halt the process and request the user for additional information. From the UI, I send them a dialog. If they cancel the dialog, I reset the value in the VM that is associated with the ComboBox controls SelectedValue.
This is what I've observed. When the operation is cancelled by the user, my VM property is being set to the new value. However, the ComboBox is still showing the text value of the original entry that has now changed. How can I force the ComboBox to update itself from within my PropertyChanged event? In this case, I think it's just a textual issue or numeric index change that's referencing the text data from the bound collection. The data is correct in the VM but the display value for the ComboBox is wrong.
EXAMPLE
ComboBox Details
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
Height="27" />
Sorry for the wordy properties on the VM, but that's to explain what's happening. My ListOfComboBoxDisplayObjects collection represents a set of enumerator values that are stored in the path within SelectedValuePath. The descriptive text for each value is pulled from the ListOfComboBoxDisplayObjects which is a special list strictly created for the UI. This basically pairs an enumeration value with a meaningful description.
ListOfComboBoxDisplayObjects Definition (from within VM)
Edit #1 - Added this definition to my example
private ObservableCollection<BindableEnumerationItem<Values>> _listOfComboBoxDisplayObjects;
public ObservableCollection<BindableEnumerationItem<Values>> ListOfComboBoxDisplayObjects
{
get { return _listOfComboBoxDisplayObjects; }
private set
{
if (value != _listOfComboBoxDisplayObjects)
{
_listOfComboBoxDisplayObjects= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ListOfComboBoxDisplayObjects)));
}
}
}
MySelectionIsAnEnumeration Definition (From within VM)
*Edit #1: Adding this code definition.
private Values_mySelectionIsAnEnumeration ;
public Values MySelectionIsAnEnumeration
{
get { return _mySelectionIsAnEnumeration; }
set
{
//Double-checked this-- value is different on the second-call to change this value, once the UI cancels the operation.
if (value != _mySelectionIsAnEnumeration)
{
_mySelectionIsAnEnumeration= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MySelectionIsAnEnumeration )));
}
}
}
Pertinent Values Associated with ListOfComboBoxDisplayObjects
These values are generated in the ctor of the VM. They are fixed throughout the application.
Item #1
Text: "This is a Foo!"
Value: Values.Foo
Item #2:
Text: "Hi, I'm Bar."
Value: Values.Bar
Item #3:
Text: "This is Baz. I need to ask a question before I can be used."
Value: Values.Baz
PropertyChanged Event - From the UI Back-End
private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MySelectionIsAnEnumeration":
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if(!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
break;
}
}
After executing the above code, what I'm observing is that the VM.MySelectionIsAnEnumeration value is indeed changing to the proper value of Value.Foo when a user cancels the operation within AskAQuestionAndGetAResult(). However, after it's finished the ComboBox still reads "This is Baz. I need to ask a question before I can be used.", which is obviously the display value associated with Value.Baz.
How can I update both the underlying VM property AND the display text on the CombobBox to correctly show the valued that is now stored in VM.MySelectionIsAnEnumeration?
Edit #2
Below is the code efor my BindableEnumerationItem that I use within my Observable Collections for comboxes and list boxes. This is used throughout my application in simpler cases and has caused no issue. Please note, this is my actual, unaltered code. I've not renamed anything. My comboboxes can bind to each Item property for a type-safe property and DisplayText is the descriptor text.
public class BindableEnumerationItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private T _item;
public BindableEnumerationItem(T item, string displayText)
{
_item = item;
_displayText = displayText;
}
private string _displayText;
public string DisplayText
{
get { return _displayText; }
set
{
if (value != _displayText)
{
_displayText = value;
PropertyChanged(this, new PropertyChangedEventArgs("DisplayText"));
}
}
}
public T Item
{
get { return _item; }
set
{
_item = value;
PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
}
create an extension that will wire up the command from your viewmodel in xaml to the selector, which in this case is the combobox.
public partial class Extensions
{
public static readonly DependencyProperty SelectionChangedCommandProperty = DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Selector;
if (element != null)
{
element.SelectionChanged -= OnSelectionChanged;
if (e.NewValue != null)
{
element.SelectionChanged += OnSelectionChanged;
}
}
}));
public static ICommand GetSelectionChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(UIElement element, ICommand value)
{
element.SetValue(SelectionChangedCommandProperty, value);
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var element = sender as Selector;
var command = element.GetValue(SelectionChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element.SelectedItem))
{
command.Execute(element.SelectedItem);
e.Handled = true;
}
}
}
Create the command in the viewmodel that handles the value changed event.
public ICommand EnumerationValueChangedCommand
{
get
{
return new Command(
() =>
{
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if (!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
});
}
}
And then bind using that extension. ext is the namespace for your extensions.
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
ext:Extensions.SelectionChangedCommand="{Binding EnumerationValueChangedCommand}"
Height="27" />

Derived property never called

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");
}
}

How to use INotifyPropertyChanged on a property in a class within a class..?

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.

Using INotifyPropertyChanged in WPF

Hi i am trying to use the NotifyPropertyChanged to update all the places where i am binding a property. For what i have searched the INotifyPropertyChanged is indicated to this cases.
So i need help because i don't understand what i have wrong in here. And i really don't know what to do with the PropertyChange event. My question about that is, when he changes? What i have to more with him?
Datagrid:
<ListView Name="listView" ItemsSource="{Binding Categories}"/>
An example when i change my Categories property:
DataTest dtTest = new DataTest();
public MainWindow()
{
InitializeComponent();
this.DataContext = dtTest;
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
//Here i pick up a string from a textBox, where i will insert in a Table of my DB,
//Then i will do a query to my Table, and i will get a DataTable for example
//Then i just put the contents into the DataView, so the value have changed.
dtTest.Categories = dtTable.DefaultView;
dtTest = dtTable.defaultView; (this only an example, i don't this for real.)
//What i have to do now, to wherever i am binding (DataGrid, ListView, ComboBox)
//the property to the new values automatically being showed in all places?
}
My Class:
public class DataTest : INotifyPropertyChanged
{
private DataView categories;
public event PropertyChangedEventHandler PropertyChanged; //What i have to do with this?
private void NotifyPropertyChanged(string str)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(str));
}
}
public DataView Categories
{
get { return categories; }
set
{
if (value != categories)
{
categorias = value;
NotifyPropertyChanged("Categories");
}
}
}
}
My Class with INotifyCollectionChanged:
public class DataTest : INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
CollectionChanged(this, e);
}
}
public DataView Categories
{
get { return categories; }
set
{
if (value != categories)
{
categories = value;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, Categories));
}
}
}
}
But why the PropertyChanged is always NULL??? I have to do something more, but i don't know what.
Thanks in advance!
the DataTest class needs to be the DataContext for the Binding. You can set this in code-behind (or a myriad of other ways, but for simplicity - just do it in code-behind)
public MainWindow() // constructor
{
this.DataContext = new DataTest();
}
Then, with the 'Source' of the Binding set, you can specify the 'Path', so your xaml looks like this:
<ListBox ItemsSource="{Binding Categories}" />
Now, if the property 'Categories' is changed in code, the NotifyPropertyChanged code you have written will alert the Binding, which in turn will access the public getter for the property and refresh the view.
Getting the handler prevents the event handler from going to null after you check for null and checking for null will prevent you from getting a null exception if there are no event handlers.
The reason that your PropertyChanged is null is that there are no event handlers attached to it. The reason there are not handlers attached to it is you have not bound your object to anything (which will take care of adding a handler) or you haven't added a handler to it (if you wanted to observe it for some other reason). Once your object is created you need to bind it somewhere.
You are doing all you have to do. An event is just a special kind of delegate. You declare it, you invoke it, clients subscribe to it. That's all there is to it.

WPF: Adding an element to a databound Collection (a Dependency Property)

I have this DependencyProperty which holds an entity with a property that is a collection (ShoutBox.Entities):
public static readonly DependencyProperty ShoutBoxProperty = DependencyProperty.Register("ShoutBox",typeof (ShoutBox),typeof (ShoutBoxViewerControl));
public ShoutBox ShoutBox
{
get { return (ShoutBox) GetValue(ShoutBoxProperty); }
set { SetValue(ShoutBoxProperty, value); }
}
It is being binded in xaml like such:
<ItemsControl ItemsSource="{Binding ShoutBox.Entries}">
.
.
</ItemsControl>
When I bind it the first time, it works as expected but there are times when I need to add items to the collection (with a method that is in the same control), like such:
public void AddNewEntry(ShoutBoxEntry newEntry)
{
Dispatcher.Invoke(new Action(() =>{
ShoutBox.Entries.Add(newEntry); //Adding directly the the Dependency property
}));
}
The problem is that when I add a new element with the above method, the item isn't being displayed in the ItemsControl.
My question is, why isn't the new element that I am adding isn't being displayed in the ItemsControl ?
[Edit]
Entries (ShoutBox.Entries) is of type List<ShoutBoxEntry>
What is the type of Entries? It either needs to be ObservableCollection or implement ICollectionChanged. Otherwise the binding doesn't know that a new item has been added.
Changing the type of Entries should indeed solve the problem...
If you want to avoid the explicit call to Dispatcher.Invoke, I wrote a collection that raises the CollectionChanged and PropertyChanged events on the thread that created the collection :
public class AsyncObservableCollection<T> : ObservableCollection<T>
{
private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public AsyncObservableCollection()
{
}
public AsyncObservableCollection(IEnumerable<T> list)
: base(list)
{
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
RaiseCollectionChanged(e);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post(RaiseCollectionChanged, e);
}
}
private void RaiseCollectionChanged(object param)
{
// We are in the creator thread, call the base implementation directly
base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the PropertyChanged event on the current thread
RaisePropertyChanged(e);
}
else
{
// Post the PropertyChanged event on the creator thread
_synchronizationContext.Post(RaisePropertyChanged, e);
}
}
private void RaisePropertyChanged(object param)
{
// We are in the creator thread, call the base implementation directly
base.OnPropertyChanged((PropertyChangedEventArgs)param);
}
}
More details can be found here :
http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/

Categories

Resources