I have a ComboBox bound to a ViewModel Items Source List.
The ComboBox also has a SelectionChanged Event.
I want the event to fire only when the user has clicked the ComboBox and selected a new item.
But the event fires at application startup when the bound items are loaded and the default item is programmatically selected.
XAML
<ComboBox x:Name="cboDisplay"
ItemsSource="{Binding Display_Items}"
SelectedValue="{Binding Display_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Height="22"
Width="100"
SelectionChanged="cboDisplay_SelectionChanged" />
C#
private void cboDisplay_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Event Fired");
}
ViewModel
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
public MainViewModel()
{
// Default Item at Startup
Display_SelectedItem = "Windowed";
}
// Items Source
//
private List<string> _Display_Items = new List<string>()
{
"Fullscreen",
"Windowed"
};
public List<string> Display_Items
{
get { return _Display_Items; }
set
{
_Display_Items = value;
OnPropertyChanged("Display_Items");
}
}
// Selected Item
//
private string _Display_SelectedItem { get; set; }
public string Display_SelectedItem
{
get { return _Display_SelectedItem; }
set
{
if (_Display_SelectedItem == value)
{
return;
}
_Display_SelectedItem = value;
OnPropertyChanged("Display_SelectedItem");
}
}
Related
I have a ComboBox in my View:
<ComboBox Name="comboBox1" ItemsSource="{Binding MandantList}" SelectedItem="{Binding CurrentMandant, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Firma}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Here is my Model:
public class MandantListItem : INotifyPropertyChanged
{
public MandantListItem() { }
string _Firma;
bool _IsChecked;
public string Firma
{
get { return _Firma; }
set { _Firma = value; }
}
public bool IsChecked
{
get
{
return _IsChecked;
}
set
{
_IsChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And here is my ViewModel:
public class MaViewModel : INotifyPropertyChanged
{
public ObservableCollection<MandantListItem> MandantList { get { return _MandantList; } }
public ObservableCollection<MandantListItem> _MandantList = new ObservableCollection<MandantListItem>();
private MandantListItem _CurrentMandant;
public MandantListItem CurrentMandant
{
get { return _CurrentMandant; }
set
{
if (value != _CurrentMandant)
{
_CurrentMandant = value;
OnPropertyChanged("CurrentMandant");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
How to fill the ComboBox:
public zTiredV2.ViewModel.MaViewModel MAList = new zTiredV2.ViewModel.MaViewModel();
this.comboBox1.ItemsSource = MAList.MandantList;
MAList.MandantList.Add(new zTiredV2.Model.MandantListItem { Firma = "A", Homepage = "a.com", IsChecked = false });
MAList.MandantList.Add(new zTiredV2.Model.MandantListItem { Firma = "B", Homepage = "b.com", IsChecked = false });
But my item doesnt update ... tried also via IsChecked, but no success either ... when i iterate through MAList, IsChecked is always false. And how can i bind a TextBlock to the selected Firma?
Have a hard time with MVVM, but i like it.
You should set the DataContext of the ComboBox to an instance of your view model. Otherwise the bindings won't work:
this.comboBox1.DataContext = MAList;
Also note that the _MandantList backing field for your property shouldn't be public. In fact, you don't need it at all:
public ObservableCollection<MandantListItem> MandantList { get; } = new ObservableCollection<MandantListItem>();
Setting the DataContext should cause the CurrentMandant property to get set when you select an item in the ComboBox. It won't set the IsChecked property though.
I have a ViewModel (VM is just the code behind to simplify the example) which contains (among other things) a Pallet class. On the pallet there are many boxes. In each box there are some pieces.
I have a Wpf form the lists each box on the pallet with a textbox showing how many items are in the box. Underneath this is a label showing the total count of all items on the pallet.
My question is how to get the label to update when one of the textboxes get changed. I think this will have something to do with property change and collection changed events, but I can't seem to get it to work.
I've come across answers that seem to work for adding or removing items from the collection. The problem is that I'm not ading or removing any items. I am only changing a value on an item in the collection. I know there are a lot of questions about this issue, but I haven't been able to find one that works for me.
Here is an example program:
MainWindow.xaml
<Window>
<StackPanel>
<ItemsControl ItemsSource="{Binding Pallet.BoxesOnPallet}" AlternationCount="100" Tag="{Binding .}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- Cast 1 -->
<TextBox Text="{Binding NumberOfPiecesinBox}" Margin="10" Padding="3"></TextBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Text="{Binding Total}" Padding="3"></TextBlock>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.ComponentModel;
namespace Keops.Mes.Casthouse.Entities.BO
{
public class Box : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Box(int num)
{
NumberOfPiecesinBox = num;
}
public int NumberOfPiecesinBox
{
get { return _numberOfPiecesinBox; }
set
{
_numberOfPiecesinBox = value;
OnPropertyChanged("NumberOfPiecesinBox");
}
}
public int _numberOfPiecesinBox;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Pallet.cs
using System.Collections.ObjectModel;
using System.Linq;
using Keops.Mes.Casthouse.Entities.BO;
namespace WpfApplication2
{
public class Pallet
{
public Pallet()
{
BoxesOnPallet = new ObservableCollection<Box>
{
new Box(3),
new Box(8),
new Box(5),
new Box(1),
new Box(0)
};
}
public ObservableCollection<Box> BoxesOnPallet { get; set; }
public int ItemTotal
{
get { return BoxesOnPallet.Sum(x => x.NumberOfPiecesinBox); }
set { }
}
}
}
Box.cs
using System.ComponentModel;
namespace Keops.Mes.Casthouse.Entities.BO
{
public class Box : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Box(int num)
{
NumberOfPiecesinBox = num;
}
public int NumberOfPiecesinBox
{
get { return _numberOfPiecesinBox; }
set
{
_numberOfPiecesinBox = value;
OnPropertyChanged("NumberOfPiecesinBox");
}
}
public int _numberOfPiecesinBox;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In this example, to update the total, your Pallet class would need to "watch" for items being added and removed from BoxesOnPallet by handling its CollectionChanged. That handler should then hook / unhook the PropertyChanged event of the added / removed item. The handler for that event can update the Total property on Pallet. It's a bit complicated to get everything working together.
public class Pallet : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Pallet()
{
BoxesOnPallet = new ObservableCollection<Box>();
BoxesOnPallet.CollectionChanged += BoxesOnPallet_CollectionChanged;
BoxesOnPallet.Add(new Box(3));
BoxesOnPallet.Add(new Box(8));
BoxesOnPallet.Add(new Box(5));
BoxesOnPallet.Add(new Box(1));
BoxesOnPallet.Add(new Box(0));
}
private void BoxesOnPallet_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
((Box)item).PropertyChanged += Box_Changed;
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.OldItems)
{
((Box)item).PropertyChanged -= Box_Changed;
}
}
}
void Box_Changed(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Box.NumberOfPiecesinBox))
{
OnPropertyChanged(nameof(BoxesOnPallet));
}
}
public ObservableCollection<Box> BoxesOnPallet { get; set; }
public int ItemTotal
{
get { return BoxesOnPallet.Sum(x => x.NumberOfPiecesinBox); }
set { }
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Box : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Box(int num)
{
NumberOfPiecesinBox = num;
}
public int NumberOfPiecesinBox
{
get { return _numberOfPiecesinBox; }
set
{
_numberOfPiecesinBox = value;
OnPropertyChanged(nameof(NumberOfPiecesinBox));
}
}
public int _numberOfPiecesinBox;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
First, you need to implement INotifyPropertyChanged in Pallet class so you create a way of telling the view to re-read the correct value; then, you need to monitor PropertyChanged event of every item in the collection so you could tell whether a property has changed, while keeping the monitored items list synced with the items in collection.
Pallet.cs:
public class Pallet : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Pallet()
{
BoxesOnPallet = new ObservableCollection<Box>
{
new Box(3),
new Box(8),
new Box(5),
new Box(1),
new Box(0)
};
}
private ObservableCollection<Box> _boxesOnPallet;
public ObservableCollection<Box> BoxesOnPallet
{
get { return _boxesOnPallet; }
set
{
if (_boxesOnPallet != null)
{
foreach (Box box in _boxesOnPallet)
{
if (box != null)
box.PropertyChanged -= Box_PropertyChanged;
}
_boxesOnPallet.CollectionChanged -= BoxesOnPallet_CollectionChanged;
}
_boxesOnPallet = value;
if (value != null)
{
foreach (Box box in value)
{
if (box != null)
box.PropertyChanged += Box_PropertyChanged;
}
value.CollectionChanged += BoxesOnPallet_CollectionChanged;
}
OnPropertyChanged(nameof(BoxesOnPallet));
}
}
private void BoxesOnPallet_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e?.OldItems != null)
{
foreach (Box box in e.OldItems)
{
if (box != null)
box.PropertyChanged -= Box_PropertyChanged;
}
}
if(e?.NewItems != null)
{
foreach (Box box in e.NewItems)
{
if (box != null)
box.PropertyChanged += Box_PropertyChanged;
}
}
}
private void Box_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(nameof(Box.NumberOfPiecesinBox)))
OnPropertyChanged(nameof(ItemTotal));
}
public int ItemTotal
{
get { return BoxesOnPallet.Sum(x => x.NumberOfPiecesinBox); }
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Also, the binding mentioned in the XAML file seems to be OneWay which means value will be fetched from the source to the view and not vice versa; instead, this should be a TwoWay binding.
MainWindow.xaml:
...
<TextBox Text="{Binding NumberOfPiecesinBox, Mode=TwoWay}" Margin="10" Padding="3"/>
...
I am trying to capture the SelectedItem for a ListBox when then data template click event is triggered for the Button. I put a breakpoint in the notifypropertychanged event handler, but it never triggers. What am I doing wrong here?
xaml:
<ListBox x:Name="lstbox_playerContainer"
ItemsSource="{Binding ChildObjectOC}"
SelectedItem="{Binding SelectedChildObject, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Name="btn_childButton" Click="btn_childButton_Click"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
c#:
public partial class PlayerCrowdPromptPage: INotifyPropertyChanged {
public PlayerCrowdPromptPage() {
InitializeComponent();
DataContext = this;
}
private ObservableCollection<PlayerCrowdObjectBO> childObjectOC = new ObservableCollection<PlayerCrowdObjectBO>();
public ObservableCollection<PlayerCrowdObjectBO> ChildObjectOC {
get {
return childObjectOC;
}
set {
childObjectOC = value;
}
}
private PlayerCrowdObjectBO selectedChildObject;
public PlayerCrowdObjectBO SelectedChildObject {
get { return selectedChildObject; }
set {
selectedChildObject = value;
OnPropertyChanged("SelectedChildObject");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null){
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Clicking on the Button won't automatically select the corresponding item. You may select it programmatically in the event handler though:
private void btn_childButton_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
ListBoxItem lbi = lstbox_playerContainer.ItemContainerGenerator.ContainerFromItem(btn.DataContext) as ListBoxItem;
lbi.IsSelected = true;
}
This should set the SelectedChildObject property and raise the PropertyChanged event.
Consider the following Sample Code :
View.xml
<Grid>
<ListView Name="NameList" HorizontalAlignment="Left" Height="142" Margin="55,45,0,0" VerticalAlignment="Top" Width="389">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding FirstName}"/>
<Label Content="{Binding LastName}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Button" HorizontalAlignment="Left" Margin="120,256,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
View.xml.cs
public partial class MainWindow : Window
{
ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
this.DataContext = vm;
NameList.ItemsSource = vm.fn;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.fn.Add(new fullname("P", "Q"));
vm.fn[0].FirstName = "NewName";
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
fn = new ObservableCollection<fullname>();
fn.CollectionChanged += ContentCollectionChanged;
fn.Add(new fullname("A", "B"));
fn.Add(new fullname("C", "D"));
}
public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (fullname item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (fullname item in e.NewItems)
{
//Added items
item.PropertyChanged += EntityViewModelPropertyChanged;
}
}
}
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//This will get called when the property of an object inside the collection changes
}
public ObservableCollection<fullname> _fn;
public ObservableCollection<fullname> fn
{
get { return _fn; }
set { _fn = value; OnPropertyChanged("fn"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
}
}
}
Model.cs
class fullname : INotifyPropertyChanged
{
public fullname(string f, string l)
{
FirstName = f;
LastName = l;
}
public string _FirstName;
public string FirstName
{
get { return _FirstName; }
set { _FirstName = value; OnPropertyChanged("FirstName"); }
}
public string _LastName;
public string LastName
{
get { return _LastName; }
set { _LastName = value; OnPropertyChanged("LastName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
}
}
}
If I add or remove any items in the ObservableCollection it updates the ListView in the View correctly but the problem is that if i modify the ObservableCollection item property the ListView is not updated.
for ex:
On click of above specified button it
a. Adds a new item (Successfully reflected in the ListView)
b. Modify FirstName of first item (Not reflected in the ListView)
What should i do so as to make the modifications reflect in View.
I would be very thankful if anyone could point out what i am doing wrong.
ObservableCollection event are not fired when an item is changed, only when it is added, removed or moved.
You must implement INotifyPropertyChanged interface in item class, and register each item event in collection.
See here
If you make heavy changes in the collection, you can raise a
NotifyCollectionChangedAction.Reset
event to reset them all
See here
I was also struggling with the same issue on WinUI with ListView and ObservableCollection.
What you need to understand is that ObservableCollection is only responsible to notify collection changes. Item level change are handle through INotifyPropertyChanged. However for those to be applied you need to make sure your binding mode is at least one way, two way if you need to.
Your XAML should look like that:
Text="{x:Bind Title, Mode=OneWay}
Do not attempt what's proposed there, it's just wrong:
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
I have a ViewModel Called HaemogramViewModel
Here is code:
public class HaemogramViewModel : INotifyPropertyChanged
{
public HaemogramViewModel()
{
}
public Haemogram CurrentHaemogramReport
{
get
{
return MainWindowViewModel.cHaemogram;
}
set
{
MainWindowViewModel.cHaemogram = value;
OnPropertyChanged("CurrentHaemogramReport");
}
}
protected virtual void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
In my MainWindowViewModel Calss:
class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
cHaemogram = new Haemogram();
}
public static Haemogram cHaemogram { get; set; }
private void SaveChanges(object obj)
{
using (Lab_Lite_Entities db = new Lab_Lite_Entities())
{
//db.Patients.Add(CurrentPatient);
if (cHaemogram != null)
{
if (cHaemogram.Haemoglobin != null)
{
db.Haemograms.Add(cHaemogram);
}
}
}
}
}
My textbox is bound to the field Haemoglobin of CurrentHaemogram Property.
When I enter some value in the textbox and then click save button then everything works fine.
Now the problem is :
When I enter some value in the textbox then I press tab and then again click on textbox and then clear the value in the textbox. Now if I click on save button then I don't get the textbox's value = null, instead I get the textbox's value = the value that I entered previously.
Try this it works
<TextBox Text="{Binding B, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}"/>
and in you view model property should be declared as below
private double? b;
public double? B
{
get
{
return b;
}
set
{
b = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("B"));
}
}
}
In your xmal you have to set the property UpdateSourceTrigger=PropertyChanged as below
<TextBox Text="{Binding Path=Property, UpdateSourceTrigger=PropertyChanged}"/>
By defalut UpdateSourceTrigger=LostFocus, that means the property bound to the textBox will get updated once you press tab or it's focus is lost. If you set to PropertyChanged it will update the property for every char change in the textBox