UI not updating for bound element - c#

My property updates just fine, but my user interface is not updated.
What am i doing wrong?
I also tried setting the DataContext not in XAML, but in the code behind constructor, but that didn't work either.
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
TestCommand = new RelayCommand(UpdateTest);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(null, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string _test;
public string Test
{
get { return _test; }
set
{
_test = value;
NotifyPropertyChanged();
}
}
public ICommand TestCommand { get; set; }
public void UpdateTest()
{
Test += "test ";
}
}
View:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Test}" />
<Button Grid.Row="1" Content="Test 2" Command="{Binding TestCommand}" />
</Grid>
</Window>

You are not implementing PropertyChanged correctly. The event model for .NET requires that the sender argument of the invoked delegates is set to the reference of the object actually raising the event. You set that value to null. Your code should use this instead:
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Note that for thread safety, you should also not use the "check and raise" pattern on the event field itself. You should instead store the field in a local variable, check the local variable, and then raise the event from that variable if non-null. The above, which uses the ?. operator ("null conditional operator") effectively does this; the compiler generates the local variable implicitly for you and ensures that the reference won't change between the time you check it for null and the time you actually try to use it.

Related

Why doesn't a ListBox bound to an IEnumerable update?

I have the following XAML:
<Window x:Class="ListBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ListBoxTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Model />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Items}" Grid.Row="0"/>
<Button Content="Add" Click="Button_Click" Grid.Row="1" Margin="5"/>
</Grid>
</Window>
and the following code for the Model class, which is put into main window's DataContext:
public class Model : INotifyPropertyChanged
{
public Model()
{
items = new Dictionary<int, string>();
}
public void AddItem()
{
items.Add(items.Count, items.Count.ToString());
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
private Dictionary<int, string> items;
public IEnumerable<string> Items { get { return items.Values; } }
public event PropertyChangedEventHandler PropertyChanged;
}
and main window's code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var model = this.DataContext as Model;
model.AddItem();
}
}
When pressing the button, the contents of the list are not being updated.
However, when I change the getter of the Items property to this:
public IEnumerable<string> Items { get { return items.Values.ToList(); } }
it starts to work.
Then, when I comment out the part which sends PropertyChanged event it stops working again, which suggests that the event is being sent correctly.
So if the list receives the event, why can't it update its contents correctly in the first version, without the ToList call?
Raising the PropertyChanged event for the Items property is only effective if the property value has actually changed. While you raise the event, the WPF binding infrastructure notices that the collection instance returned by the property getter is the same as before and does nothing do update the binding target.
However, when you return items.Values.ToList(), a new collection instance is created each time, and the binding target is updated.

Windows store app - two elements one object binding

What is the best way to acchieve this, what I am going to describe bellow.
I have two textboxes with twoway bindings on the same object and same property.
Now, when I update text in one textbox I wish other textbox to grab the same value again from object. Is that even possible, or I have to do this manually. For an example, I can use TextChanged event and set this value.
Yes you can bind a single property to two controls
If this class is your DataContext (viewmodel)
public class Bind : INotifyPropertyChanged
{
private string _text1;
public string text1
{
get
{
return _text1;
}
set
{
_text1=value;
NotifyPropertyChanged("text1");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
In XAML
<UserControl x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525"
xmlns:ViewModel="clr-namespace:WpfApplication1">
<UserControl.DataContext>
<ViewModel:Class1/>
</UserControl.DataContext>
<Grid>
<TextBox Width="150" Height="50" Text="{Binding text1, Mode=TwoWay}"/>
<TextBox Text="{Binding text1, Mode=TwoWay}" Margin="0,232,0,0"/>
</Grid>
</UserControl>

Super simple binding example

I'm trying to do a simple binding but I'm having some problems. I have a text block and a button. The textblock is binded to a property called "word". When you press the button the value of word changes and I want to automacally update the text block. This is a classic example, please explain me what I'm doing wrong:
namespace WpfApplication5
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _word;
public string word
{
get { return _word; }
set
{
_word= value;
RaisePropertyChanged(word);
}
}
private void change_Click(object sender, RoutedEventArgs e)
{
word= "I've changed!";
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
And my XAML with the binding:
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock HorizontalAlignment="Left" Margin="210,152,0,0" TextWrapping="Wrap" Text="{Binding word}" VerticalAlignment="Top"/>
<Button x:Name="change" Content="Change" HorizontalAlignment="Left" Margin="189,235,0,0" VerticalAlignment="Top" Width="75" Click="change_Click"/>
</Grid>
</Window>
You are raising a PropertyChanged event for a property named I've changed!, because you pass the value of the property word to RaisePropertyChanged. You need to pass the name of the property instead:
RaisePropertyChanged("word");
This answer assumes that the data context is set correctly. If not, you need to fix that too:
DataContext = this;

Why is the Dependency Property not returning its value?

I have a MyUserControl with the following Xaml:
<TextBox Text="{Binding InputValueProperty}" />
In the MyUserControl.xaml.cs I have:
public string InputValue
{
get { return (string)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value); }
}
public static readonly DependencyProperty InputValueProperty =
DependencyProperty.Register("InputValueProperty", typeof(string),
typeof(MyUserControl));
In my MainWindow.xaml I create a user control:
<local:MyUserControl InputValue="My Input" />
Later on in my MainWindow.xaml.cs I am trying to access this string. All instances of MyUserControl are contained in a List and I access them with a foreach.
string temp = userControl.InputValue;
This is always null. In my MainWindow.xaml I can see the "My Input" in the text box of the user control but I can't ever seem to get it out of there.
DependencyProperty.Register("InputValueProperty", ...
That should be:
DependencyProperty.Register("InputValue", ...
XAML depends on the registered name of the property, not the name of the property accessor.
It looks like the problem is in your binding. Here's a working example that's modeled off your code with a relative source binding:
Here's the user control:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public string InputValue
{
get { return (string)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value); }
}
public static readonly DependencyProperty InputValueProperty =
DependencyProperty.Register("InputValueProperty", typeof(string),
typeof(MyUserControl));
}
<UserControl x:Class="WpfApplication4.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication4" Height="30" Width="300">
<Grid>
<TextBox Text="{Binding Path=InputValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyUserControl}}}" />
</Grid>
</UserControl>
And here's the window:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string text1 = ctrl1.InputValue;
string text2 = ctrl2.InputValue;
string text3 = ctrl3.InputValue;
//breakpoint here
}
}
<Window x:Class="WpfApplication4.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication4" Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<local:MyUserControl x:Name="ctrl1" InputValue="My Input" />
<local:MyUserControl x:Name="ctrl2" InputValue="2" />
<local:MyUserControl x:Name="ctrl3" InputValue="3" />
<Button Click="Button_Click" Height="25" Content="debug"/>
</StackPanel>
</Grid>
</Window>
If i throw a breakpoint in the click event i can see the bound values of each of the controls. (if you copy and paste from this be sure to change WpfApplication4 to whatever your project is called.
You need to implement INotifyPropertyChanged on your class that has the property
public class YourClassThatHasTheInputValuePropertyInIt: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string InputValue
{
get { return (string)GetValue(InputValueProperty); }
set { SetValue(InputValueProperty, value);
NotifyPropertyChanged("InputValue"); }
}
}
This will allow the binding to pick up the property

Listbox Not binding to a BindingList<T>

I have a ListBox on a form that is bound to a BindingList<T> in code behind but is not displaying the items within the BindingList<T>.
XAML:
<Window x:Class="MessageServer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MessageServer"
Name="mainWindow" Title="Message Exchange Server"
Height="350" Width="525" Closing="Window_Closing">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox Name="OutputList" Grid.Row="0" />
<ListBox Name="Connected" Grid.Row="1" ItemsSource="{Binding ElementName=mainWindow, Path=ConnectedClients}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=FullIPAddress}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
CodeBehind:
private BindingList<Client> _ConnectedClients;
public BindingList<Client> ConnectedClients
{
get { return _ConnectedClients; }
set { _ConnectedClients = value; }
}
public class Client : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TcpClient _tcpClient;
public TcpClient tcpClient
{
get { return _tcpClient; }
set
{
_tcpClient = value;
NotifyPropertyChanged();
}
}
public string FullIPAddress
{
get { return _tcpClient.Client.RemoteEndPoint.ToString(); }
}
public string IPAddress
{
get { return _tcpClient.Client.RemoteEndPoint.ToString().Split(':').ElementAt(0); }
}
public string PortNumber
{
get { return _tcpClient.Client.RemoteEndPoint.ToString().Split(':').ElementAt(1); }
}
public Client(TcpClient tcpClient)
{
this.tcpClient = tcpClient;
NotifyPropertyChanged();
}
private void NotifyPropertyChanged()
{
NotifyPropertyChanged("tcpClient");
NotifyPropertyChanged("FullIPAddress");
NotifyPropertyChanged("IPAddress");
NotifyPropertyChanged("PortNumber");
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Any Ideas why the list box is not displaying the items?
Not sure if this is worth mentioning but When added items to the BindingList this is done on a seperate thread to the UI Thread. but I have tried using Dispatcher.BeginInvoke() but still does not work...
It sounds like you really want to use ObservableCollection. It sounds like BindingList should work, but on this SO post they seem say ObservableCollection is for WPF and BindingList for Winforms: Differences between BindingList and ObservableCollection
Try using an ObservableCollection<T>. It was designed specifically for WPF.
You are trying to bind to Window.ConnectedClients, which is a property that doesn't exist.
You need to change your binding to DataContext.ConnectedClients to bind to Window.DataContext.ConnectedClients
ItemsSource="{Binding ElementName=mainWindow, Path=DataContext.ConnectedClients}"

Categories

Resources