I have the following setup:
XAML:
<ListBox x:Name="MyList" ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Height="20" Width="20" Visibility="{Binding HasInformation, Converter={StaticResource VC}, ConverterParameter=True}" Source="/path/to/information.png" />
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Padding="5,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note: The ConverterParameter being passed in simply controls whether the visibility is "Collapsed" (False), or "Hidden" (True), so in this case, I want the visibility to be Hidden.
ViewModel Snippet:
private ObservableCollection<IItem> _MyItems;
public ObservableCollection<IItem> MyItems
{
get
{
return _MyItems;
}
set
{
NotifyPropertyChanged(ref _MyItems, value, "MyItems");
}
}
private IItem _SelectedItem;
public IItem SelectedItem
{
get
{
return _SelectedItem;
}
set
{
NotifyPropertyChanged(ref _SelectedItem, value, "SelectedItem");
}
}
IItem:
public interface IItem
{
string Name { get; }
bool HasInformation { get; set; }
}
I populate an implementation of a list of IItem from a database into the list, and the information icon appears appropriately if HasInformation is true. This all works correctly.
However, if I set HasInformation by hand, the view does not update. I have tried:
In the ViewModel:
OnPropertyChanged("MyItems");
MyItems[MyItems.IndexOf(SelectedItem)].HasInformation = true;
// Note that "SelectedItem" is persisted correctly, and always
// points to the selected item that we want to update.
In the code behind:
MyList.GetBindingExpression(ItemsControl.ItemsSourceProperty).UpdateTarget();
All of these fire the getter of the MyItems property, but the view never updates and the icon never displays. I have ensured that the HasInformation property of the item that I updated does, in fact, remain true. I've attached to the PropertyChanged event to ensure that it's firing a property change for "MyItems" (it is, this also fires the getter), and I've even ensured that it's calling the value converter with the correct value for the HasInformation property (it is!), so what am I missing? Is there something weird with image showing/hiding or visibility value conversion that I'm not handling correctly?
ObservableCollection only notifies the collection changes not the changes in each of the item. In order to achieve your goal one of options is to change the IItem from interface to a class which implements INotifyPropertyChanged interface (or implement it in the IItem concrete type), and hook it with the ViewModel's PropertyChanged delegate (remember to unsubscribe it). See some of my code below.
ViewModel
public class MyViewModel: INotifyPropertyChanged
{
private ObservableCollection<Item> _MyItems;
public ObservableCollection<Item> MyItems
{
get
{
return _MyItems;
}
set
{
if (_MyItems != null)
{
foreach (var item in _MyItems)
{
item.PropertyChanged -= PropertyChanged;
}
}
if (value != null)
{
foreach (var item in value)
{
item.PropertyChanged += PropertyChanged;
}
}
OnPropertyChanged();
}
}
private Item _SelectedItem;
public Item SelectedItem
{
get
{
return _SelectedItem;
}
set
{
_SelectedItem = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Item
public class Item : INotifyPropertyChanged
{
private string _name;
private bool _hasInformation;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public bool HasInformation
{
get { return _hasInformation; }
set
{
_hasInformation = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Related
I'm new to MVVM in WPF and I have the following problem.
What I try to have is two ComboBoxes, each binding to the same ObservableCollection<TwoProperties> DList property as ItemsSource and with synchronized SelectedItem, so I wrote this in my XAML
<ComboBox ItemsSource="{Binding DList}" DisplayMemberPath="Property1" SelectedItem="{Binding SelectedD}" />
<ComboBox ItemsSource="{Binding DList}" DisplayMemberPath="Property2" SelectedItem="{Binding SelectedD}" />
with this viewmodel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TwoProperties> _dList =
new ObservableCollection<TwoProperties> {
new TwoProperties(1,"one"),
new TwoProperties(2,"two")
};
public ObservableCollection<TwoProperties> DList
{
get { return _dList; }
set { _dList = value; OnPropertyChanged("DList"); }
}
private TwoProperties _selectedD;
public TwoProperties SelectedD
{
get { return _selectedD; }
set { _selectedD = value; OnPropertyChanged("SelectedD"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
where
public class TwoProperties
{
public double Property1 { get; set; }
public string Property2 { get; set; }
public TwoProperties (double p1, string p2)
{
Property1 = p1;
Property2 = p2;
}
}
I would also like to have two TextBoxes that display the properties of the currently SelectedItem of the synchronized ComboBoxes. The properties Property1 and Property2 of SelectedD should be editable, however the ObservableCollection<TwoProperties> _dList should remain constant/readonly and not change its values.
<TextBox Text="{Binding SelectedD.Property1}" />
<TextBox Text="{Binding SelectedD.Property2}" />
But when I edit the TextBoxes and therefore SelectedD, also _dList changes its values, which is not what I want.
I hope I could explain my problem. I'm sure I'm missing something simple here.
This could be implemented easily by changing the binding mode for the TextBoxes into one way as following:
<TextBox Text="{Binding SelectedD.Property1,Mode=OneWay}" />
<TextBox Text="{Binding SelectedD.Property2,Mode=OneWay}" />
Thus when you change the textBox value, the changes would not be reflected back to the Observable collection objects.
Note that you can get rid of magical strings in your view model OnPropertyChanged by modifying the method as following:
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And then you can call it inside the setter of any property inside the view model without passing the name of the property as following:
private TwoProperties _selectedD;
public TwoProperties SelectedD
{
get { return _selectedD; }
set { _selectedD = value; OnPropertyChanged(); }
}
Edit 2:
Update my binding and view model to get the edited values inside the view model
View model updates:
private double? editPropertyOne;
public double? EditPropertyOne
{
get { return editPropertyOne; }
set
{
editPropertyOne = value;
OnPropertyChanged();
}
}
private string editPropertyTwo;
public string EditPropertyTwo
{
get { return editPropertyTwo; }
set
{
editPropertyTwo = value;
OnPropertyChanged();
}
}
private TwoProperties _selectedD;
public TwoProperties SelectedD
{
get { return _selectedD; }
set
{
_selectedD = value; OnPropertyChanged();
if (_selectedD != null)
{
EditPropertyOne = _selectedD.Property1;
EditPropertyTwo = _selectedD.Property2;
}
}
}
Xaml changes:
<TextBox Text="{Binding EditPropertyOne}" />
<TextBox Text="{Binding EditPropertyTwo}" />
I am on a MVVM C# project.
I want to display a list of objects.
I want to add and remove items in this list and ALSO change items in this list.
So I choosed the BindingList<> over the ObservableCollection<>, which would not get noticed if an item has changed.
(I also tested the ObservableCollectionEx which is out there in the web, but this has the same behavior like the BindingList for me).
But the Listbox is not changing when items are changed.
(Adding and removing items is updated in the Listbox)
In my XAML
<ListBox DisplayMemberPath="NameIndex" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}">
or alternative with the ItemTemplate
<ListBox DockPanel.Dock="Right" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameIndex}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel (ViewModelBase is implementing INotifyPropertyChanged etc)
public class ProfileListViewModel : ViewModelBase
{
private BindingList<Profile> profiles;
public BindingList<Profile> Profiles
{
get
{
return profiles;
}
set
{
profiles = value;
RaisePropertyChanged();
}
}
My items are also implementing INotifyPropertyChanged and I am calling OnPropertyChanged("Name") in my Setters.
My model
public class Profile : INotifyPropertyChanged
{
public Profile(){}
public int ProfileID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wiring the View with the ViewModel (BindingList is initialized before View)
ProfileListViewModel plvw= new ProfileListViewModel(message.Content);
var profileView = new ProfileListView(plvw);
profileView.ShowDialog();
In the View.xaml.cs
public ProfileListView(ProfileListViewModel plvw)
{
InitializeComponent();
DataContext = plvw;
}
When I am changing the name of an object then I get the ListChanged event to which I have subscribted in my ViewModel (Profiles.ListChanged += Profiles_ListChanged;) for testing BUT the items in the ListBox are NOT changing.
What am I doing wrong?
How can I get a updated Listbox?
Since your DisplayIndex is the computed property NameIndex, you need to call OnPropertyChanged("NameIndex") when its value changes due to a change in other properties, e.g.:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameIndex");
}
}
Use
Profiles.ResetBindings() to bind it again.
I've seen this question posted (and answered) a number of times, and I still can't seem to figure out what I'm missing...
I have a window with a list of checkboxes, and I want the ability to have checkboxes in the list enabled/disabled dynamically from code-behind. To do that I've got couple of radio buttons that call a code-behind function to toggle the 'Enabled' property of the first entry in the VisibleFeatures collection. Ideally, this would cause the first checkbox + text to enable/disable, but no UI changes occur.
What am I doing wrong?
ViewModel:
public class MyFeature
{
private bool _supported;
private bool _enabled;
private bool _selected;
public string Name { get; set; }
public bool Supported
{
get { return _supported; }
set { _supported = value; NotifyPropertyChanged("Supported"); }
}
public bool Enabled
{
get { return _enabled; }
set { _visible = value; NotifyPropertyChanged("Enabled"); }
}
public bool Selected
{
get { return _selected; }
set { _selected = value; NotifyPropertyChanged("Selected"); }
}
public MyFeature(string name)
{
Name = name;
_supported = false;
_enabled = false;
_selected = false;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public ObservableCollection<MyFeature> VisibleFeatures { get; set; }
void VisibleFeatures_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (MyFeature item in e.NewItems)
item.PropertyChanged += MyFeature_PropertyChanged;
if (e.OldItems != null)
foreach (MyFeature item in e.OldItems)
item.PropertyChanged -= MyFeature_PropertyChanged;
}
void MyFeature_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// NotifyPropertyChanged() defined again elsewhere in the class
NotifyPropertyChanged("VisibleFeatures");
}
public Init()
{
VisibleFeatures = new ObservableCollection<MyFeature>();
VisibleFeatures.CollectionChanged += VisibleFeatures_CollectionChanged;
VisibleFeatures.Add(new MyFeature("Feature1"));
VisibleFeatures.Add(new MyFeature("Feature2"));
...
}
XAML:
<StackPanel>
<ListView ItemsSource="{Binding VisibleFeatures}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel IsEnabled="{Binding Enabled, Mode=TwoWay}">
<CheckBox IsChecked="{Binding Selected, Mode=TwoWay}">
<TextBlock Text="{Binding Name}" />
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</StackPanel>
Your class MyFeature needs to declare that it implements interface INotifyPropertyChanged. Otherwise, there will be no listener generated from XAML to listen to your property change notification.
Beside, from your example, I see no use of notifying VisibleFeatures change.
Derive your class "MyFeature" from INotifyPropertyChanged interface.
Inorder to reflect your runtime changes made in your observable collection in view, it is mandatory to derive your viewmodel class (here MyFeature class) from INotifyPropertyChanged interface.
Also, it is advisable to use same instance of your binding property wherever it is used instead of creating a new instance.
I have the code:
<TextBox Width="200" Text="{Binding Value}"></TextBox>
Which works. However the "Value" can be different types. So if I have an bool I want to show a checkbox. I rewrote it as as follow, which kinda works:
<ContentControl Content="{Binding Value}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=.}"></CheckBox>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Double}">
<TextBox Width="200" Text="{Binding Path=.}"></TextBox>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
But now the property isn't updated like before. I have tried setting Mode=Twoway, but it still do not work.
Edit
It worked perfectly fine when I only had the textbox, editing the text of the textbox updated the model. However when I tried doing this with the second code (ContentControl) it just doesn't work.
Code
I'm using Mvvm-light togheter with bindings. The "Value" is bound to an instance of Property
[JsonObject]
public class Property<T> : INotifyPropertyChanged
{
[JsonProperty]
public String name;
public Property(String name, T value)
{
this._value = value;
this.name = name;
}
[JsonIgnore]
public T Value {
get { return _value; }
set {
_value = value;
hot = true;
NotifyPropertyChanged("Value");
}
}
[JsonProperty(PropertyName = "value")]
private T _value;
[JsonIgnore]
public String Name { get { return name; } set { name = value; } }
[JsonProperty]
public bool hot = false;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You should implement INotifyPropertyChanged interface in order to track property changes. I'm sure everything works fine then.
This works for me:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object value;
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
DataContext = this;
}
public object Value
{
get { return value; }
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Value = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I'm trying to data bind to a custom data type property FormulaField in WPF. I don't understand if there's something I've missed or if what I'm trying to do can't be done?
I've followed the convention of how I've bound to a primitive and found that hasn't worked, there's not update on the FormulaField property. I've also noticed that the custom data type set method is never hit. I'm using MVVM.
A model:
public class OBQModel : NotificationObject
{
private FormulaField _tovLitres;
public FormulaField TOVLitres
{
get
{
if (_tovLitres.UsesFormula)
{
_tovLitres.Value = ConversionHelper.USBarrelsToLitres(_tovBarrels);
}
return _tovLitres;
}
set
{
_tovLitres = value;
RaisePropertyChanged("TOVLitres");
}
}
}
The NotificationObject implements INotifyPropertyChanged:
public abstract class NotificationObject : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<Func<T>> action)
{
var propertyName = GetPropertyName(action);
RaisePropertyChanged(propertyName);
}
private static string GetPropertyName<T>(Expression<Func<T>> action)
{
var expression = (MemberExpression)action.Body;
var propertyName = expression.Member.Name;
return propertyName;
}
protected internal void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
FormulaField looks like this:
public class FormulaField
{
public bool UsesFormula { get; set; }
public double Value { get; set; }
}
EDIT
Implementing INotifyPropertyChanged in FormulaField goes stack overflow...
public class FormulaField : INotifyPropertyChanged
{
public bool UsesFormula { get; set; }
public double Value
{
get
{
return Value;
}
set
{
Value = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
The Models sit inside an ObservableCollection in a ViewModel.
An illustration of the View:
<StackPanel>
<DataGrid ItemsSource="{Binding OBQModelCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="new TOV (L)" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox BorderThickness="0"
Text="{Binding TOVLitres.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
Based upon what you wrote, you are raising INPC on "LiquidGOVLitres", which doesn't seem to appear in your code listing, but you are binding to "TOVLitres".
Fixing this inconsistency will help, but you will also need to implement INPC on the FormulaField if you want changes to its members to be part of your UI.
ETA: After the clarifying edit to your code listing, the remaining task is to implement INPC on your FormulaField class and raise the event accordingly.
Also, if you are using 4.5 you can investigate the new Member Info class which helps avoid the use of magic strings in INPC.
Finally, for semantic clarity, it wouldn't hurt to rename "Value" to "FormulaValue"...
To avoid recursion, try this model...
private double _value;
public double Value
{
[DebuggerStepThrough]
get { return _value; }
[DebuggerStepThrough]
set
{
if (Math.Abs(value - _value) > Double.Epsilon)
{
_value = value;
OnPropertyChanged("Value");
}
}
}