WPF - runtime updated binding question - c#

I'm quite a novice with C# and a complete newbie regarding WPF. Probably it's a very basic question and piece of cake for the pros. Please bear with me.
I need to display a dynamic textblock with text changed in run time without additional triggers such as button clicks and so. For some reason (my insufficient understanding of the concept obviously) textblock stays empty.
Xaml is as simple as it can be:
<Window x:Class="WpfApplication1.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" Loaded="Window_Loaded">
<Grid>
<TextBlock Text="{Binding Path=Name}"/>
</Grid>
</Window>
And the code behind it simplified as well:
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Client client = new Client();
client.Name = "Michael";
Thread.Sleep(1000);
client.Name = "Johnson";
}
}
public class Client : INotifyPropertyChanged
{
private string name = "The name is:";
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return this.name;
}
set
{
if (this.name == value)
return;
this.name = value;
this.OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, e);
}
}
}
Thanks in advance,
Ceres

In order for the binding to work you need to set the DataContext of the window to the object you want to bind to, in this instance the client object.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Client client = new Client();
// Set client as the DataContext.
DataContext = client;
client.Name = "Michael";
Thread.Sleep(1000);
client.Name = "Johnson";
}
This should cause the TextBox to successfully update.
Just to point out that using Thread.Sleep() in the loaded event causes the program to hang for a second on startup, a better idea would be to use the WPF DispatcherTimer to create the 1 second delay.
Hope that helps!

Related

Property change not updating bound control property [duplicate]

I'm trying to simplify some code by putting the ViewModel models into the code behind and binding the DataContext as "this", but it seems to work differently, in the following example:
Why is it when the button is clicked, the TextBlock bound to "Message" does not change, even though OnPropertyChanged("Message") is called?
XAML:
<Window x:Class="TestSimple223.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel HorizontalAlignment="Left">
<Button Content="Button"
Click="button1_Click" />
<TextBlock
Text="{Binding Path=Message, Mode=TwoWay}"/>
<TextBlock
x:Name="Message2"/>
</StackPanel>
</Window>
Code Behind:
using System.Windows;
using System.ComponentModel;
namespace TestSimple223
{
public partial class Window1 : Window
{
#region ViewModelProperty: Message
private string _message;
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
#endregion
public Window1()
{
InitializeComponent();
DataContext = this;
Message = "original message";
Message2.Text = "original message2";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Message = "button was clicked, message changed";
Message2.Text = "button was click, message2 changed";
}
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
You haven't marked your class as being available for property change notification. Change the heading to
public partial class Window1 : Window, INotifyPropertyChanged
Just because you implement the methods doesn't mean that WPF knows that a class supports change notification - you need to tell it by marking it with INotifyPropertyChanged. This way, the binding mechanism can identify your class as a potential update target.

WPF Update grid visibility

After some search on web I have set up this simple example:
PropertyChangedBase.cs
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action)(() => {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
UserViewModel.cs
public class UserViewModel : PropertyChangedBase
{
private Visibility _showUserWindow = Visibility.Collapsed;
public Visibility ShowUserWindow {
get { return _showUserWindow; }
set {
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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>
<Grid Margin="43,28,247,129" Background="AliceBlue" Visibility="{Binding ShowUserWindow}"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="349,150,0,0" VerticalAlignment="Top" Width="75" PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
UserViewModel userViewModel;
public MainWindow() {
InitializeComponent();
userViewModel = new UserViewModel();
DataContext = userViewModel;
}
private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
userViewModel.ShowUserWindow = Visibility.Visible;
Thread.Sleep(1000);
userViewModel.ShowUserWindow = Visibility.Collapsed;
}
}
Right now grid becomes collapsed after 1 sec, I would like to update UI before timer starts. What I am doing wrong?
Edit:
Thread.Sleep line immitates some work, that takes some time to complete.
Grid should become visible before work starts and show some info about that work and become collapsed after work is done.
Well, you should consider doing the Thread.Sleep(1000) operation on a separate thread, not on the UI thread. Check this for that.
Other than that, try to use yourGrid.UpdateLayout() method after setting its visibility to collapsed.
LE:
Most probably, his Thread.Sleep(1000) stands for something like a database operation, for example, something that takes time.
LE2: A BackgroundWorker will do the trick. Check this link!
If you are using .NET 4.5, you can use Task.Delay() to allow UI to update itself before starting the actual work:
private async void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
userViewModel.ShowUserWindow = Visibility.Visible;
await Task.Delay(1);
Thread.Sleep(1000);
userViewModel.ShowUserWindow = Visibility.Collapsed;
}
Please note that await Task.Delay(1); should be used even after you replace Thread.Sleep(1000) with actual work.
However this should be used only if your work can be done only in UI thread and cannot be moved to a background thread (for example, massive loading of UI elements). Otherwise the correct approach is to move the work to a background thread:
private async void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
userViewModel.ShowUserWindow = Visibility.Visible;
await Task.Start(() => {
Thread.Sleep(1000);
};
userViewModel.ShowUserWindow = Visibility.Collapsed;
}

Updating dynamically the content property of a label with the WPF user model

A simple WPF window with a label on it:
<Window x:Name="MainWindow1" x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:A="clr-namespace:WpfApplication1"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="384" Width="669.114" Icon="alarmclock.ico" Closing="OnClosing" Loaded="OnLoaded" >
<Grid x:Name="Grid1">
<Label x:Name="Label1" Content="{Binding AlarmStatus}" HorizontalAlignment="Left" Margin="10,314,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
Wire up an object so that the object can be bound to the labels content property. Yes I see the text Off on the window:
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
public class UserViewModel : PropertyChangedBase
{
private string _alarmStatus = "Off";
public string AlarmStatus
{
get { return _alarmStatus; }
set
{
_alarmStatus = value;
OnPropertyChanged("AlarmStatus"); //This is important!!!
}
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
I thought I would be able to change the value of the label like this:
private void OnLoaded(object sender, RoutedEventArgs e)
{
//Im setting a value but the UI does not change. The label still says Off
UserViewModel aaaa = new UserViewModel();
aaaa.AlarmStatus = "On";
}
You're creating a new instance of the ViewModel, but the View has no "connection" to that.
the aaaa instance dies (goes out of scope and is de-referenced) immediately after the OnLoaded() method finishes executing, and is garbage collected a moment later.
What you need is to grab the actual instance of the ViewModel that's currently being used by the Window:
private void OnLoaded(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as UserViewModel;
viewModel.AlarmStatus = "On";
}
I suggest you read Rachel's Article to better understand the concept of DataContext in WPF.

When does binding actually happen

On a popup window I have a checkbox.IsChecked bound to a model, but I want to check its state from the xaml code behind when the window is displayed. When checking the checkbox by name in the code behind from the windows loaded event it is not set yet. There are some UI specific things that I need to do and that is why I need to know what the checkboxes value is when the window opens and i cannot perform this from the model that the checkbox.IsChecked is bound to.
The property on the model is set long before the popup window is opened, so it is not an issue of the binding not being there. I figured that once the Loaded event fires the window would be ready to use bindings and all, but this does not seem to be the case.
Xaml:
<RefinedRibbonControls:RefinedRibbonGroup Header="Show Graphs">
<StackPanel x:Name="panelVisibilities">
<CheckBox Content="Show/hide" x:Name="myCheckBox"
IsChecked="{Binding Path=Processor.Model.IsItemVisible}"
Click="GraphVisibilityClickEvent"
HorizontalAlignment="Left"/>
...etc
Property on model:
public bool IsItemVisible
{
get { return _isItemVisible ; }
set
{
if (_isItemVisible != value)
{
_isItemVisible = value;
_propertyChanger.FirePropertyChanged(this, m => m.IsItemVisible);
}
}
}
Event in Xaml codebehind:
private void WindowLoadedEvent(object sender, RoutedEventArgs e)
{
if(myCheckBox.IsChecked.Value)
{
// Do UI Related Stuff
}
}
The binding works fine and the values show up when the window is displayed, the problem is I cannot get the value of the binding in the window loaded event.
Edit: Possible solution I have found but I am not sure if its the best way.
I called the following method from the constructor on the xaml code behind.
private void SetupInitialVisibility()
{
//Fire after everything is loaded.
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
IEnumerable<CheckBox> elements = this.panelVisibilities.Children.OfType<CheckBox>().ToList();
foreach (CheckBox checkBox in elements)
{
if (checkBox.IsChecked != null && checkBox.IsChecked.Value == false)
{
//Do work
}
}
}));
}
found at: https://stackoverflow.com/a/1746975/1253746
Data binding is not done synchronously, but it is delayed. Check this msdn page on dispatcher priorities. It is done at a lower priority than normal window messages, but before rendering.
You could invoke a method on yourself with a lower priority than is defined for databinding, in this method you should be able to safely read the data bound value.
I would still find this ugly. I'd rather subscribe directly to PropertyChanged and check for this property, or even better, rewrite your "UI related code" as a data binding.
P.S. If you start consuming events, be sure to unsubscribe, or you might get memory leaks.
DataBinding should precede the Loaded event I think.
When and how do you set your DataContext? And you are positive that the viewmodel property is already set?
The following works, try to align your code with this if possible.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<CheckBox x:Name="myCheckBox" IsChecked="{Binding IsItemVisible}" />
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if (myCheckBox.IsChecked.Value)
{
//...
}
}
}
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isItemVisible;
public bool IsItemVisible { get { return isItemVisible; } set { isItemVisible = value; OnPropertyChanged("IsItemVisible"); } }
public ViewModel()
{
this.IsItemVisible = true;
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Why does OnPropertyChanged not work in Code Behind?

I'm trying to simplify some code by putting the ViewModel models into the code behind and binding the DataContext as "this", but it seems to work differently, in the following example:
Why is it when the button is clicked, the TextBlock bound to "Message" does not change, even though OnPropertyChanged("Message") is called?
XAML:
<Window x:Class="TestSimple223.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel HorizontalAlignment="Left">
<Button Content="Button"
Click="button1_Click" />
<TextBlock
Text="{Binding Path=Message, Mode=TwoWay}"/>
<TextBlock
x:Name="Message2"/>
</StackPanel>
</Window>
Code Behind:
using System.Windows;
using System.ComponentModel;
namespace TestSimple223
{
public partial class Window1 : Window
{
#region ViewModelProperty: Message
private string _message;
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
#endregion
public Window1()
{
InitializeComponent();
DataContext = this;
Message = "original message";
Message2.Text = "original message2";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Message = "button was clicked, message changed";
Message2.Text = "button was click, message2 changed";
}
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
You haven't marked your class as being available for property change notification. Change the heading to
public partial class Window1 : Window, INotifyPropertyChanged
Just because you implement the methods doesn't mean that WPF knows that a class supports change notification - you need to tell it by marking it with INotifyPropertyChanged. This way, the binding mechanism can identify your class as a potential update target.

Categories

Resources