How can i set binding to childproperty from App.xaml - c#

I have a problem with my Binding to a ListBox Control.
Actually i have a Property in App.xaml.cs :
public partial class App : Application, INotifyPropertyChanged
{
ObservableCollection<Panier> _panier = new ObservableCollection<Panier>();
public ObservableCollection<Panier> PanierProperty
{
get { return _panier; }
set
{
if (this._panier != value)
{
this._panier = value;
NotifyPropertyChanged("PanierProperty");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
this property have a child properties in the "Panier class" here:
public class Panier : INotifyPropertyChanged
{
private string _nom;
private string _category;
private int _prix;
public string Nom
{
get { return _nom; }
set
{
if (this._nom != value)
{
this._nom = value;
NotifyPropertyChanged("Nom");
}
}
}
public string Category
{
get { return _category; }
set
{
if (this._category != value)
{
this._category = value;
NotifyPropertyChanged("Category");
}
}
}
public int Prix
{
get { return _prix; }
set
{
if (this._prix != value)
{
this._prix = value;
NotifyPropertyChanged("Prix");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
and in my MainWindow.xaml page i have my ListBox bound to PanierProperty(parent property) :
<telerik:RadListBox x:Name="PanierLB" Grid.Row="1" Height="200" Width="350" Margin="0 300 0 0"
ItemsSource="{Binding PanierProperty, Source={x:Static Application.Current}}"
DisplayMemberPath="{Binding Path=PanierProperty.Nom, Source={x:Static Application.Current}}">
</telerik:RadListBox>
my problem is that PanierProperty is bound to my Listbox i see items in the listbox like Design.Panier
Design.Panier
Design.Panier
etc...
I dont know how to get the PanierProperty.Nom(Nom is the child property) to show on the ListBox.
Someone can help please.

In DisplayMemberPath use the name of property you want to show only:
<telerik:RadListBox
...
DisplayMemberPath="Nom"

Related

Updating calculated total when a property on the underlying item in a list is changed - wpf

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"/>
...

Binding a string variable to a label

I am trying to move from WinForms to WPF, and am stuck on binding.
I have a label:
<Label Name="labelState" Content="{Binding state}" HorizontalAlignment="Right" Margin="10,10,10,10" FontSize="12" />
In the cs of the same userControl (named FormInput), I have :
public string state { get; set; }
public FormInput()
{
state = "ok";
InitializeComponent();
}
Why doesn't this work?
Thank you.
When you are binding something in WPF you need to use INotifyPropertyChanged
Implement a class follows,
class TestObject : INotifyPropertyChanged
{
private string _state;
public string State
{
get
{
return _state;
}
set
{
if (_state == value) return;
_state = value;
OnPropertyChanged("State");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
and in your FormInput
public FormInput()
{
InitializeComponent();
TestObject t = new TestObject();
labelState.DataContext = t;
t.State = "ok";
}
and XAML as follows,
<Label Name="labelState" Content="{Binding State}" HorizontalAlignment="Right" >

MVVM: how model can use inotifypropertychanged to notify viewmodel of changes

I need Model to notify ViewModel if any property is changed, because I need to collect the changed model instances in a collection for further processing, also to enable and disable command buttons in the viewmodel.
So I've used ModelBase abstract class and added HasChanges property which I can test against in the viewmodel and catch the changed models.But it is not working and I don't know what i'm missing.
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
The Model is wrapped inside the ViewModel and bound to the View which is a DataGrid:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
Thanks for valuable help.
I tested your class and it's okay. I think the point is a typo here:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
There should be RaisePropertyChanged instead of NotifyPropertyChanged.
Below it's my test:
XAML
<Window x:Class="TestUpdatePropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:TestUpdatePropertyChanged"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<this:TestViewModel />
</Window.DataContext>
<Grid>
<TextBox Width="100" Height="25" Text="{Binding Path=TestString, UpdateSourceTrigger=PropertyChanged}" />
<Button Width="100" Height="30" VerticalAlignment="Top" Content="Click" Click="Button_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var testData = this.DataContext as TestViewModel;
testData.TestString = "Yay, it's change!";
if (testData.HasChanges == true)
{
MessageBox.Show("It's Change!");
}
}
}
public class TestViewModel : ModelBase
{
private string _testString = "test";
public string TestString
{
get { return _testString; }
set
{
if (_testString != value)
{
_testString = value;
RaisePropertyChanged("TestString");
}
}
}
}
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}

Value of the property does not change when the textbox is cleared

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

Changing Background color property of listbox item via databinding

I am trying to make ListBox which updates its content according to some changing data.
The XAML is as follows
StackPanel Orientation="Vertical">
<ListBox x:Name="listWatch" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid ShowGridLines="True">
<Grid Grid.Column="0" Background="{Binding Path=Color">
<TextBlock Text="{ Binding Path=LTP}" Padding="2 2 2 2"/>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="btn" Click="btn_Click" Content="Button" />
The class i used for form data strucure is as follows
public class WatchRow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _color;
decimal _lTP;
public WatchRow(decimal LTP,string color)
{
this.LTP = LTP;
this.Color = color;
}
public string Color
{
get { return _color; }
set{
_color = value;
OnPropertyChanged(_color);
}
}
public decimal LTP
{
get { return _lTP; }
set
{
_lTP = value;
OnPropertyChanged(_lTP.ToString());
}
}
protected void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
public class Watch:ObservableCollection<WatchRow>
{
public Watch():base()
{
}
}
And the code behind file is like
Watch watch = new Watch();
private void PhoneApplicationPage_Loaded_1(object sender, RoutedEventArgs e)
{
watch.Add(new WatchRow(132, "black"));
watch.Add(new WatchRow(123, "red"));
listWatch.ItemsSource = watch;
watch[0].Color = "green";
}
private void btn_Click(object sender, RoutedEventArgs e)
{
watch[0].Color = "green";
}
My problem is that i am not able to change the color of the list box item by setting the color property(watch[0].Color = "green";) in btn_Click as shown in the code. But the same code works in PhoneApplicationPage_Loaded_1. I don't know what i'm wrong. Any Ideas?
I believe the problem is a slight change to how you are using PropertyChanged. When you are calling OnPropertyChanged, pass through the name of the property that you are changing, rather than the value. For example:
public string Color
{
get { return _color; }
set{
_color = value;
OnPropertyChanged(_color);
}
}
Should be:
public string Color
{
get { return _color; }
set{
_color = value;
OnPropertyChanged("Color");
}
}
Also, I'm not sure if this is necessarily a problem, but this is how I've always created the OnPropertyChanged function:
Instead of:
protected void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
Try:
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
Also, as Magnus Johansson mentioned, define a brush, and bind the Color, rather than a string. So the Color property would be (see his explanation for further details on this):
private Color _color;
public Color Color
{
get { return _color; }
set{
_color = value;
OnPropertyChanged("Color");
}
}
Using Mvvm you can resolve your problem:
I've tested this code and it works. You need to split the code in three separate files, like this:
the viewModel
public class WatchViewModel
{
public ObservableCollection<WatchRow> WatchRows { get; set; }
public void GetWatchRows()
{
WatchRows= new ObservableCollection<WatchRow>();
}
public void AddWatchRow(int value, string color)
{
var item=new WatchRow(value, color);
WatchRows.Add(item);
}
}
The model
public class WatchRow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _color;
decimal _lTP;
public WatchRow(decimal LTP, string color)
{
this.LTP = LTP;
this.Color = color;
}
public string Color
{
get { return _color; }
set
{
_color = value;
OnPropertyChanged(_color);
}
}
public decimal LTP
{
get { return _lTP; }
set
{
_lTP = value;
OnPropertyChanged(_lTP.ToString());
}
}
protected void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
And the view (code behind of your xaml page)
public partial class MainPage : PhoneApplicationPage
{
private WatchViewModel watch;
public MainPage()
{
InitializeComponent();
watch = new WatchViewModel();
watch.GetWatchRows();
watch.AddWatchRow(132, "green");
watch.AddWatchRow(123, "red");
base.DataContext = watch;
listWatch.ItemsSource = watch.WatchRows;
}
private void btn_Click(object sender, RoutedEventArgs e)
{
watch.AddWatchRow(132, "pink");
watch.AddWatchRow(113, "yellow");
}
}

Categories