WPF ~ Trouble with Binding & INotifyPropertyChanged - c#

WPF n00bie here, trying to get his UI to work properly.
So I made this test example. The textblock bound to HeaderText1 changes correctly at the launch of the app, but the textblock bound to HeaderText2 doesn't update after clicking the button.
What am I doing wrong? Thanks in advance!!
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=DataContext.HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=DataContext.HeaderText2}"></TextBlock>
</StackPanel>
</Window>
Main window class:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DataBinding
{
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
public DataContextSample()
{
HeaderText = "YES";
InitializeComponent();
this.DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
Source source = new Source();
source.HeaderText2 = "YES2";
}
}
}
And the INotifyPropertyChanged class
using System.ComponentModel;
namespace DataBinding
{
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set; get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
}

First of all you are doing many things wrong.
You should not be using the window as it's own datacontext, you should have a viewmodel that you set.
You should not be using event handlers in the view to manipulate the viewmodel. You should bind the button to a command.
Your source seems to be a "viewmodel", consider renaming it to MainWindowViewModel (for clarity) and then do this.
public class MainWindowViewModel : INotifyPropertyChanged
{
private string headerText;
private string headerText2;
private ICommand updateHeaderText2;
public string HeaderText
{
set
{
return this.headerText;
}
get
{
this.headerText = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText");
}
}
public string HeaderText2
{
set
{
return this.headerText2;
}
get
{
this.headerText2 = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText2");
}
}
public ICommand UpdateHeaderText2
{
get
{
// Google some implementation for ICommand and add the MyCommand class to your solution.
return new MyCommand (() => this.HeaderText2 = "YES2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And set this viewmodel to the datacontext of your window.
this.DataContext = new MainWindowViewModel();
And then in your xaml you should bind to the viewmodel as such
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<!-- Not sure what this binding is? -->
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Command="{Binding UpdateHeaderText2}" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding HeaderText}"></TextBlock>
<TextBlock Text="{Binding HeaderText2}"></TextBlock>
</StackPanel>
</Window>

You set the DataContext to this (the window). You don't have a property named HeaderText2 in the DataContext so the second binding won't work.
I'd do this (without changing your code too much, in reality I'd do a proper MVVM approach):
public partial class DataContextSample : Window
{
public Source Source { get; set; }
public string HeaderText { set; get; }
public MainWindow()
{
InitializeComponent();
HeaderText = "YES";
Source = new Source { HeaderText2 = "YES" };
DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
Source.HeaderText2 = "YES2";
}
}
I added a new property called Source which is of type Source. Set its initial HeaderText2 to the same "YES" in the constructor and in the button click change that to "YES2".
You have to change your Source class as well, to actually notify about changes:
public class Source : INotifyPropertyChanged
{
private string _headerText2;
public string HeaderText2
{
get { return _headerText2; }
set
{
_headerText2 = value;
OnPropertyChanged("HeaderText2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And then in your XAML:
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=Source.HeaderText2}"></TextBlock>
</StackPanel>

Well there are a few issues with your code.
First of all, you never assign your "Source" to a datacontext, so there's no way for your second TextBlock to find the value of "HeaderText2".
If however you would assign your "Source" to the textblocks datacontext then we could fetch the value of "HeaderText2". Consider the code below
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Name="TextBlock2" Text="{Binding Path=HeaderText2}"></TextBlock>
</StackPanel>
</Window>
We have given your second Textblock a name, "TextBlock2" and also removed the "Datacontext"-part from your binding.
Then we have moved the Creation of your "Source" object from the button event to the windows constructor (there is no need to make a new one everytime we click a button when all we want to do is to update a property)
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
private Source source { get; set; }
public DataContextSample()
{
...
source = new Source();
TextBlock2.DataContext = source;
...
}
...
}
And then in your buttons click-event we assign your databound property a value of "YES2".
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
...
source.HeaderText2 = "YES2";
}
There is however one more detail. Your class "Source" does implement "INotifyPropertyChanged", but it never "uses" it. By that I mean, that when you assign a value to your property "HeaderText2" you never actually "notify" the UI that something has changed with it, and thus the UI will not fetch the new value. Consider the code below:
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set
{
headerText2 = value;
OnPropertyChanged("HeaderText2");
}
get
{
return headerText2;
}
}
string headerText2;
...
}
So let's take a look at what we've done with the property "HeaderText2". Everytime the "HeaderText2" gets a value assigned, it will first save the value in a privat property (so that we can read from it later). But in addition to that we also call the "OnPropertyChanged" method with our Propertys name. That method will in turn check if anyone is "listening" to our "PropertyChanged"-event (and since we have a databinding on the current object, someone is listening), and create a new event.
Now we have assigned a datasource to your textblock with a path to "HeaderText2", we are notifying all listeners when we update "HeaderText2" on the datasource and we are updating "HeaderText2" on the buttons click event.
Happy coding!

Related

creating dynamic controls with MVVM

I am trying to create a dynamic control with using mvvm for the first time. I want to generate buttons dynamically and have the content display inside of the buttons. I am sure I am missing something really easy here, but I have no idea what it could be. When I run the code, nothing appears on the interface, though I can see AvailableMonitorOC populate in the constructor...
Here is my ViewModel where I manually add buttons to an observable collection for simplicity sake of this example:
public class CreateAndDisplayViewModel {
public ObservableCollection<AvailableMonitorBo> AvailableMonitorOC = new ObservableCollection<AvailableMonitorBo>();
public CreateAndDisplayViewModel() {
availableMonitorBo = new AvailableMonitorBo();
availableMonitorBo.AvailableMonitorLabel = "Label 1";
AvailableMonitorOC.Add(availableMonitorBo);
availableMonitorBo.AvailableMonitorLabel = "Label 2";
AvailableMonitorOC.Add(availableMonitorBo);
}
private AvailableMonitorBo availableMonitorBo;
public AvailableMonitorBo AvailableMonitorBo {
get { return availableMonitorBo; }
set {
availableMonitorBo = value;
}
}
}
Here is my model:
public class AvailableMonitorBo : INotifyPropertyChanged {
private string availableMonitorLabel { get; set; }
public string AvailableMonitorLabel {
get { return availableMonitorLabel; }
set {
availableMonitorLabel = value;
OnPropertyChanged("AvailableMonitorLabel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here is the xaml:
<ListView Grid.Row="2" Grid.Column="1"
ItemsSource="{Binding AvailableMonitorOC, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding AvailableMonitorLabel}"
Width="100"
Height="25"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The main reason for you lack of display is that AvailableMonitorOC needs to be a property of CreateAndDisplayViewModel, not a field as it is currently.
You're also only creating one AvailableMonitorBo instance and changing its caption each time.

WPF data binding with inner elements

I want to bind my StackPanel component to an object and its properties to elements it the StackPanel
<StackPanel Grid.Column="0" Grid.Row="0" Name="device1" Background="#CC119EDA" DataContext="{Binding}">
<Label FontSize="22" Foreground="White">Desk #1</Label>
<TextBox Text="{Binding Name}" />
</StackPanel>
In code behind
device1.DataContext = new Class { Name = "Name" };
What is wrong with this binding? Thanks
You could try like this. When u set the property to the Object , the UI Thread is not aware of the change , so you need to Implement RaisePropertyChange Mechanism. Using MvvmLight Toolkit is great advantage . Here the window datacontext is set to so could inherit all elements.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _name;
public string MyName
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("MyName");
}
}
public MainWindow()
{
InitializeComponent();
MyName = "Eldho";
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml
<StackPanel>
<Label>Hi,</Label>
<TextBox Text="{Binding MyName}"/>
</StackPanel>

Label Binding does not work

I am trying to bind a label to an Object.Object.Property and I don't get it run.
Here is my code:
XAML
<Window x:Class="MyApp.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyWindow" Height="1120" Width="800">
<Grid Name="MyGrid">
<StackPanel Orientation="Horizontal">
<Label FontWeight="Bold" FontSize="40" Content="{Binding MyDataObject/AnotherSubObject/MyProperty}"/>
</StackPanel>
</Grid>
</Window>
And the Code:
public partial class MyWindow : Window
{
public MySubObject MyDataObject { get; set; }
public MyWindow(MySubObject object)
{
InitializeComponent();
this.MyDataObject = object; // Contains MyDataObject.AnotherObject.MyProperty
DataContext = this;
}
}
And the code for MySubObject object looks like this:
public class MySubObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
AnotherObject _AnotherObject;
public MySubObject()
{
this._AnotherObject = new AnotherObject();
this._AnotherObject.Property = "Some Value";
}
public AnotherObject AnotherObject
{
get { return _AnotherObject; }
set { _AnotherObject = value; OnPropertyChanged("AnotherObject"); }
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
I would be glad to get dome support for this case.
Use Dot(.) as binding property path separator not Forward slash(/)
<Label FontWeight="Bold" FontSize="40"
Content="{Binding MyDataObject.AnotherSubObject.MyProperty}"/>

Binding to change the property

Is it usable or this doesn't work: to change the Text Box.Text and the property behind to change can a binding of this type be made(i know that this can be made with an event from Text Box, i am looking for some kind of binding that can be made) ?
Should i just use Text Box.Text in my cod?
<TextBox Text="{Binding Path=NumeClient, Mode=TwoWay}" Height="23" HorizontalAlignment="Left" Margin="117,21,0,0" Name="textBox1" VerticalAlignment="Top" Width="249" />
public string NumeClient { get; set; }
If I understand the question correctly, you're asking how to setup a two way binding to the Text property of a TextBox?
<TextBox Text="{Binding Path=YourProperty, Mode=TwoWay}" />
This Makes both your property changes the TextBox and the TextBox changes the property (from MSDN)
Add in your class contructor DataContext = this;
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
XAML :
<TextBox Text="{Binding Path=PersonName, Mode=TwoWay}" />
Hope it helps

How can I access a label from a stackpanel that is a listBoxItem

For a list box in WPF I have this template:
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<StackPanel Orientation="Horizontal" Background="Silver">
<CheckBox Content="CheckBox" VerticalAlignment="Center"/>
<Label Content="Label" Padding="5,0" Width="260" VerticalAlignment="Center" Background="#F3D6D6D6" Margin="5,0"/>
<Button Content="Edit" Width="Auto" Padding="1" Margin="2.5,0" HorizontalAlignment="Right" Click="Button_Click"/>
</StackPanel>
</ControlTemplate>
And whenever I press the corresponding button for the listBoxItem I want to modify the label of the same listBoxItem, preferably without using a name if possible.
I was thinking maybe there is a way of saying "use the Label from the parent of this button" which I thought would be the StackPanel, but can't find anything useful on the internet.
I think the better solution is to use a view model with a DataTemplate, once you have the code set up you can re-use it over and over with very little chance of error.
Here is what your view model will look like
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ItemViewModel> _items;
public ViewModel()
{
_items = new ObservableCollection<ItemViewModel>(new List<ItemViewModel>()
{
new ItemViewModel() { Label = "Item1", IsChecked = false },
new ItemViewModel() { Label = "Item2", IsChecked = true },
new ItemViewModel() { Label = "Item3", IsChecked = true },
new ItemViewModel() { Label = "Item4", IsChecked = false },
new ItemViewModel() { Label = "Item5", IsChecked = false },
});
}
public ObservableCollection<ItemViewModel> Items
{
get
{
return this._items;
}
}
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));
}
}
}
public class ItemViewModel : INotifyPropertyChanged
{
private bool _isChecked = false;
private string _label = "Label";
public ICommand ButtonCommand { get; private set; }
public ItemViewModel()
{
this.ButtonCommand = new DelegateCommand(Com_ButtonCommand);
}
public void Com_ButtonCommand(object parameter)
{
this.Label = "New Label text";
}
public string Label
{
get
{
return this._label;
}
set
{
this._label = value;
this.OnPropertyChanged("Label");
}
}
public bool IsChecked
{
get
{
return this._isChecked;
}
set
{
this._isChecked = value;
this.OnPropertyChanged("IsChecked");
}
}
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));
}
}
}
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute,
Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
There are 3 classes here, 1 of them a helper.
ViewModel --> Your main ViewModel,
ItemViewModel --> Model for each item,
DelegateCommand --> Allows you to map the button to the view model
your xaml will look like this
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Background="Silver">
<CheckBox IsChecked="{Binding IsChecked}" Content="CheckBox" VerticalAlignment="Center"/>
<Label Content="{Binding Label}" Padding="5,0" Width="260" VerticalAlignment="Center" Background="#F3D6D6D6" Margin="5,0"/>
<Button Command="{Binding ButtonCommand}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Notice the "{Binding}" keyword, this "binds" each datatemplate to a member with that name on its own view model, in this case IsChecked and Label.
To load your ViewModel add the following line to the code-behind in your usercontrol(using MVVM you will rarely touch the code-behind of usercontrols at all).
this.DataContext = new ViewModel();
When its your first time seeing a viewmodel it may seem like a lot of work but its mostly re-usable and is the defacto standard for doing things like this (MVVM), Ive included all necessary code to get you started.
The following class as well as DelegateCommand should be kept for later use, I have already included it in the above snippet
public class ViewModelBase : INotifyPropertyChanged
{
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));
}
}
}
I'd recommend you to use a viewmodel in the back. That VM exposes a command that is bound to the button. In addition it exposes a DependencyProperty containing the name for the label. And the label is bound to that property.
Now if you press the button, the command is executed, which changes the label text and via databinding the new text will be updated on the label.
The other option, I wouldn't recommend, is to use FindName to find the Label. Or really bad (but works) is to iterate over the controls using the VisualTreeHelper.
I would navigate the VisualTree to find the parent StackPanel, then search that StackPanel to find a child Label to update.
If you're interested, I have some VisualTreeHelpers posted on my blog that would make this easy:
var parent = VisualTreeHelpers.FindAncestor<StackPanel>((Button)sender);
if (parent == null) return;
var lbl = VisualTreeHelpers.FindChild<Label>(parent);
if (lbl == null) return;
lbl.Content = "Some Text";
This is providing I'm not using the MVVM design pattern. If I were using MVVM, I would be storing the Label.Content property in the ViewModel, and the Button command should point to a Command in the ViewModel, and it should pass it the DataBound item as the CommandParameter so it knows which Label to update.
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<StackPanel Orientation="Horizontal" Background="Silver">
<CheckBox Content="CheckBox" VerticalAlignment="Center"/>
<Label Content="{Binding SomeText}" ... />
<Button Content="Edit"
Command="{Binding ElementName=MyListBox, Path=DataContext.EditCommand}"
CommandParameter="{Binding }" />
</StackPanel>
</ControlTemplate>

Categories