I'm new in WPF and I have the following problem.
I have the following class with many properties , but here is only one property for example:
public class StatusData : INotifyPropertyChanged
{
private string m_statusText = String.Empty;
public StatusData()
{
m_statusText = "1234";
}
public string StatusText
{
get
{
return m_statusText;
}
set
{
if (m_statusText != value)
{
m_statusText = value;
NotifyPropertyChanged("StatusText");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Another component of the project changes StatusData and calls Update() function in MainWindow.
So, m_statusData of this MainWindow has changed and I want update the textbox with m_statusText accordingly.
public class MainWindow
{
private StatusData m_statusData = new StatusData();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
grid1.DataContext = m_statusData ;
}
public void Update(StatusData newStatusData)
{
m_statusData = newStatusData;
}
}
Xaml code:
<Window x:Class="WpfApplicationUpdateTextBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="myWin"
xmlns:local="clr-namespace:WpfApplicationUpdateTextBox"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" >
<Grid Name="grid1">
<TextBox Text="{Binding Path=StatusText}" Name="textBox1" />
</Grid>
</Window>
The question is : why the textBox is not updated withnewStatusData.StatusText?
Here, you are assigning the grid's DataContext to m_statusData:
grid1.DataContext = m_statusData ;
And here, you are reassigning m_statusData to something else:
m_statusData = newStatusData;
The problem is that this has no effect on grid1.DataContext, which was set to the previous instance of m_statusData.
In this case, doing grid1.DataContext = newStatusData should solve your problem. However, a better solution would be to create a StatusData property which returns m_statusData. You can then do a RaisePropertyChanged() on it when m_statusData changes.
private void Update(StatusData newStatusData)
{
StatusData = newStatusData;
}
public StatusData StatusData
{
get
{
return m_statusData;
}
set
{
m_statusData = value;
RaisePropertyChanged("StatusData");
}
}
... and then in your XAML, bind your Grid's DataContext to the StatusData property
Edit:
To bind the grid's data context to the StatusData property, you can do this in your XAML:
<Grid Name="grid1" DataContext="{Binding StatusData}">
<TextBox Text="{Binding Path=StatusText}" Name="textBox1" />
</Grid>
You will also need to set the initial DataContext of your window, to make all of the other databindings work (this is a little strange and non-standard, but it will do the trick):
this.DataContext = this;
Your class StatusData has only 1 property: StatusText. If this property is the only thing you want to change with this code:
m_statusData = newStatusData;
You can change it to this:
m_statusData.StatusText = newStatusData.StatusText;
This code will fire the PropertyChangedEvent of StatusData class and that will change TextBox value.
Related
Im trying to create something like this -
I have an observable collection of points. Each point has a position and a colour. When any points position or colour changes(they implement notification change), I want to "repaint" the background gradient. Currently I have an itemscontrol where I have the sliders bound to the points position and the gradient is initially drawn. Now, I want to know how I can call a function in the code behind of my view when the propertychanged event on a 'point' fires, so that I can repaint the gradient. Im wondering if an event setter can somehow be used?
Whilst I could do the propertychanged event subscribing in code behind, I'd like to do it in XAML?
PLease note : I specifically want to take this approach of manually repainting in code behind for other reasons, so if I could get answers to the specific problem above rather than alternative solutions please.
I guess you can create an attached property to subscribe to PropertyChanged events of the value of the DataContext property.
public static class Props
{
public static DependencyProperty OnPropertyChangedProperty = DependencyProperty.RegisterAttached(
"OnPropertyChanged", typeof(PropertyChangedEventHandler), typeof(Props),
new PropertyMetadata(OnPropertyChangedPropertyChanged));
public static PropertyChangedEventHandler GetOnPropertyChanged (DependencyObject d)
{
return (PropertyChangedEventHandler)d.GetValue(OnPropertyChangedProperty);
}
public static void SetOnPropertyChanged (DependencyObject d, PropertyChangedEventHandler value)
{
d.SetValue(OnPropertyChangedProperty, value);
}
private static void OnPropertyChangedPropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var inpc = (INotifyPropertyChanged)((FrameworkElement)d).DataContext;
if (inpc == null)
throw new ArgumentException("DataContext of the framework element must not be null.");
var oldChanged = (PropertyChangedEventHandler)e.OldValue;
if (oldChanged != null)
inpc.PropertyChanged -= oldChanged;
var newChanged = (PropertyChangedEventHandler)e.NewValue;
if (newChanged != null)
inpc.PropertyChanged += newChanged;
}
}
Usage:
<Window x:Class="So17382721PropertyChangedXaml.MainWindow" x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:So17382721PropertyChangedXaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Foo}">
<!-- Here, we subscribe to DataContext.PropertyChanged;
handler is defined in the MainWindow class -->
<Grid local:Props.OnPropertyChanged="{Binding FooPropertyChanged, ElementName=root}">
<TextBox Text="{Binding Bar, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Foos, ElementName=root}"/>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace So17382721PropertyChangedXaml
{
public partial class MainWindow
{
public ObservableCollection<Foo> Foos { get; private set; }
public MainWindow ()
{
Foos = new ObservableCollection<Foo> {
new Foo { Bar = "1" },
new Foo { Bar = "2" },
new Foo { Bar = "3" },
};
InitializeComponent();
}
private void OnFooPropertyChanged (object sender, PropertyChangedEventArgs e)
{
MessageBox.Show(this, string.Format("{0} of {1} changed.", e.PropertyName, sender));
}
// Subscribing to non-RoutedEvents in XAML is not straightforward, but we can define a property
public PropertyChangedEventHandler FooPropertyChanged
{
get { return OnFooPropertyChanged; }
}
}
public class Foo : INotifyPropertyChanged
{
private string _bar;
public string Bar
{
get { return _bar; }
set
{
_bar = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Note: the attached property Props.OnPropertyChanged expects that DataContext is not changed during lifetime and is already specified. Handling DataContextChanged events is left as an exircize, if you need it.
I have question about DataContext changes and I build example for understand this aspect.
I have MainUserControl on MainWindow. MainUserControl consists of number of User Controls.
One of such User Controls is SubUserControl1.
<Window x:Class="WpfApplicationUcBindingQuestion.MainWindow">
<Grid>
.....
<uc:MainUserControl />
</Grid>
</Window>
<UserControl x:Class="WpfApplicationUcBindingQuestion.MainUserControl">
<Grid>
.....
<uc:SubUserControl1 x:Name="subUserControl1" />
</Grid>
</UserControl>
And in MainWindow I have object of class Info. Class Info consists of a few inner classes.
One of them is, lets say, SubInfo. Both Info and SubInfo classes inherits from INotifyPropertyChanged.
And this is the code of them:
public class Info : INotifyPropertyChanged
{
private SubInfo m_subInfo = new SubInfo();
public Info()
{
}
public SubInfo SubInfo
{
get
{
return m_subInfo;
}
set
{
m_subInfo = value;
OnPropertyChanged("SubInfo");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SubInfo: INotifyPropertyChanged
{
private string m_subString = "subStr";
public SubInfo()
{
}
public string SubString
{
get
{
return m_subString;
}
set
{
m_subString = value;
OnPropertyChanged("SubString");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I want to set DataContext for MainUserControl to be object of class Info
and for SubUserControl1 DataContext will be Info.SubInfo.
The following code describes this:
<UserControl x:Class="WpfApplicationUcBindingQuestion.SubUserControl1">
<Grid>
.....
<TextBox Text="{Binding Path=SubString}"/>
</Grid>
</UserControl>
public MainUserControl()
{
InitializeComponent();
MainWindow mainWnd = (MainWindow)Application.Current.MainWindow;
Info info = mainWnd.Info;
this.DataContext = info;
this.subUserControl1.DataContext = info.SubInfo;
}
When new subInfo arrived I update inner object subInfo inside info object:
(This is function of MainWindow)
private void OnUpdateData()
{
SubInfo arrivedSubInfo = new SubInfo();
arrivedSubInfo.SubString = "newString";
m_info.SubInfo = arrivedSubInfo;
}
I want to see that DataContext for subUserControl1 is also changed.
But it doesn't happened and TextBox inside SubUserControl1 is not updated
and doesn't show "newString".
(Note: If I write inside OnUpdateData() function the following:
m_info.SubInfo.SubString = arrivedSubInfo.SubString;
(copy field-field and not whole object) it works,
but I dont 'want copy 50 field...)
Where I'm wrong?
Your help will be really appreciated.
Your problem is the following:
In your constructor, when you do that:
this.DataContext = info;
this.subUserControl1.DataContext = info.SubInfo;
You will set the DataContext ONLY once. Which means it will never change unless you write subUserControl1.DataContext = someNewDataContext somewhere.
What you can do to solve this :
The "proper solution":
Use a binding. In your XAML, just write:
<uc:SubUserControl1 x:Name="subUserControl1" DataContext="{Binding
SubInfo, UpdateSourceTrigger=PropertyChanged}" />
This will work, assuming that your SubInfo property fires the OnPropertyChanged event when it is set.
The "ugly solution":
Explicitly set your UserControl's DataContext in code-behind when you need it. Again, I wouldn't advise that and you are better off applying the first solution!
I can bind a combobox in the codebehind like this:
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
var combo = sender as ComboBox;
App.SchedulerVM = new ScheduleViewModel();
combo.DataContext = App.SchedulerVM;
combo.ItemsSource = App.SchedulerVM.Frequency;
}
This works - my combobox has the items from the Frequency List in the SchedulerVM object.
However, I don't want to do any of this in the codebehind. But the ways I've done this in WP7 before aren't working here. If I comment out the last line in the Loaded method above and try to set the ItemsSource in XAML, it doesn't work - nothing shows up:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
Frequency}" />
This doesn't work either:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
App.SchedulerVM.Frequency}" />
Nor this:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
SchedulerVM.Frequency}" />
Ideally, the DataContext wouldn't have to be explicitly set in the codebehind for this control either, it would be inherited from the LayoutRoot, where I've set it in the codebehind. But that's step 2 of my troubleshooting here.
What am I doing wrong? '
Thanks!
Edit
The ScheduleViewModel looks like this:
namespace SchedulerUI.ViewModels
{
public class ScheduleViewModel : INotifyPropertyChanged
{
//private properties
private Schedule _thisSchedule;
//public properties
public Schedule ThisSchedule
{
get { return _thisSchedule; }
set
{
if (value != _thisSchedule)
{
NotifyPropertyChanged("ThisSchedule");
}
_thisSchedule = value;
}
}
public List<string> Frequency = new List<string>();
public string Test;
//constructors
public ScheduleViewModel()
{
Frequency.AddRange(new string[] { "Daily", "Weekly", "Monthly" });
Test = "This is only a test.";
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here's the entire XAML:
<UserControl x:Class="SchedulerUI.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<ComboBox Height="23" HorizontalAlignment="Left" Margin="34,41,0,0" Name="comboBox1" Loaded ="comboBox1_Loaded" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Frequency}" />
<TextBox BorderBrush="Black" HorizontalAlignment="Left" Margin="34,41,0,0" Width="100" Height="100" DataContext="LayoutRoot.DataContext" Text="{Binding Test}" />
</Grid>
</UserControl>
Here's the entire codebehind:
namespace SchedulerUI
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
App.SchedulerVM = new ScheduleViewModel();
comboBox1.DataContext = App.SchedulerVM;
List<string> testlist = App.SchedulerVM.Frequency;
string teststring = App.SchedulerVM.Test;
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
//App.SchedulerVM = new ScheduleViewModel();
//var root = sender as Grid;
//if (root != null)
//{
// root.DataContext = App.SchedulerVM;
//}
}
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
//var combo = sender as ComboBox;
//App.SchedulerVM = new ScheduleViewModel();
//combo.DataContext = App.SchedulerVM;
//combo.ItemsSource = App.SchedulerVM.Frequency;
}
}
}
You binding is not working, because:
when you set ItemsSource in XAML its get executed first and it tries to bind the wrong/empty DataContext
then the Loaded event is raised which will set the correct DataContext but your already existing binding won't be refreshed automatically.
If you have to set the DataContext in the codebehind do it in your views constructor:
public YourView()
{
InitializeComponent();
combo.DataContext = App.SchedulerVM;
}
Then the following binding should work:
<ComboBox Name="comboBox1" ItemsSource="{Binding Frequency}" />
The databinding in WPF/Silverlight needs public properties. Currently Frequency is a public field on your viewmodel change it to a property and everthing should work:
private List<string> frequency = new List<string>();
public List<string> Frequency { get { return frequency; } set { frequency = value; }
And that is why it worked your initial loaded event because you didn't used databind there but you just set the combo.ItemsSource.
I have a some XAML as follows (a simple Label and Button):
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Test="clr-namespace:WpfApplication2">
<StackPanel DataContext="{Binding Path=TestPerson}">
<Label Content="{Binding Path=Name}"></Label>
<Button Content="Button" Click="button1_Click" />
</StackPanel>
</Window>
in the code behind I have:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Person _person = new Person();
public Person TestPerson { get { return _person; } }
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_person.Name = "Bill";
//_person = new Person() { Name = "Bill" };
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TestPerson"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and the class Person is:
public class Person
{
string _name = "Bob";
public string Name
{
get { return _name; }
set { _name = value; }
}
}
As it is, firing the Propertychanged event does not cause the Label's contents to change to Bill.
I've found I am able to overcome this by either:
Assigning a new object to _person (as in the commented out line)
Removing the DataContext from the StackPanel and have Label bind to Path=TestPerson.Name
I don't understand why I have to actually assign a NEW object to _person for the Label to update, or use the FULL path in the binding.
Is there a way to update the Label without supplying the full path (relying on the DataContext), and without assigning a new object to _person?
You raise PropertyChanged for the Person instance TestPerson. However, TestPerson hasn't changed, it is the Name property of TestPerson that has changed and that is the property the Label is binding to.
Edit: To answer why your first two versions work
Assigning a new object to _person (as in the commented out line)
Here you are actually changing the value of TestPerson and because DataContext is inherited by the children, the Label gets a new DataContext as well so that's why the Binding is updated.
Removing the DataContext from the StackPanel and have Label bind to
Path=TestPerson.Name
This is something I've never seen. The same binding subscribes to PropertyChanged for both TestPerson and Name in Person so raising PropertyChanged for any of these properties will work.
If you want to overcome this without implementing INotifyPropertyChanged for Person, you can change set UpdateSourceTrigger to Explicit
<Label Name="label"
Content="{Binding Path=Name, UpdateSourceTrigger=Explicit}"/>
And update the Binding manually whenever Name changes
private void button1_Click(object sender, RoutedEventArgs e)
{
_person.Name = "Bill";
BindingExpression be = label.GetBindingExpression(Label.ContentProperty);
be.UpdateTarget();
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TestPerson"));
}
}
Otherwise, just implement INotifyPropertyChanged for Person as well and it will work
public class Person : INotifyPropertyChanged
{
string _name = "Bob";
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You need a little change in your XAML...
In your code behind, instead of setting DataContext as this, set it in XAML via Binding...
<StackPanel DataContext="{Binding RelativeSource={RelativeSource
AncestorType= {x:Type Window},
Mode=FindAncestor},
Path=TestPerson}">
<Label Content="{Binding Path=Name}"></Label>
<Button Content="Button" Click="button1_Click" />
</StackPanel>
Remove the
DataContext = this;
from your code behind.
Let me know if this helps.
I've set up a simple Silverlight 4 control which is supposed to switch the visibility of two textboxes based on a public property. I add the control to a view and set the databinding of the control's property to a property of the parent view's viewmodel.
When a change in the parent viewmodel's property occurs, nothing happens in the usercontrol. Although it's bound, the OnPropertyChanged doesnt seem to interest the bound property of the user control. Below is the code of the user control.
<UserControl x:Class="Controls.EAPPasswordBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" x:Name="_root" >
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<PasswordBox x:Name="pwdBox" />
<TextBox x:Name="txtBox" />
</StackPanel>
</Grid>
public partial class EAPPasswordBox : UserControl, INotifyPropertyChanged
{
public bool ShowText
{
get { return (bool)GetValue(ShowTextProperty); }
set {
SetValue(ShowTextProperty, value);
if (value == true)
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.pwdBox.Visibility = System.Windows.Visibility.Collapsed;
this.txtBox.Visibility = System.Windows.Visibility.Visible;
}
}
}
private Visibility _PwdBoxVisibility;
public Visibility PwdBoxVisibility
{
get { return _PwdBoxVisibility; }
set
{
_PwdBoxVisibility = value; NotifyPropertyChanged("PwdBoxVisibility");
}
}
private Visibility _TxtBoxVisibility;
public Visibility TxtBoxVisibility
{
get { return _TxtBoxVisibility; }
set
{
_TxtBoxVisibility = value; NotifyPropertyChanged("TxtBoxVisibility");
}
}
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox),null);
public EAPPasswordBox()
{
InitializeComponent();
}
private static void OnShowTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Here is how I use it in my parent view:
<local:EAPPasswordBox x:Name="pwdBox"
Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" ShowText="{Binding showPassword, Mode=TwoWay}"></local:EAPPasswordBox>
private bool _showPassword;
public bool showPassword
{
get
{
return _showPassword;
}
set
{
_showPassword = value;
RaisePropertyChanged("showPassword");
}
}
When the "showPassword" in the parent view's viewmodel changes, nothing happens in the user control, and it's driving me crazy :)
Any ideas? Thank you.
Updates to bound Dependency Properties don't occur with the normal get/set accessors of the property but behind the scenes. As such the only way to intercept when the value is changed is to provider a DependencyPropertyChangedEventHandler in the PropertyMetadata when you create the Dependency Property.
As follows:
public static readonly DependencyProperty ShowTextProperty =
DependencyProperty.Register("ShowText", typeof(bool), typeof(EAPPasswordBox), new PropertyMetadata(ShowTextPropertyChanged));
private static void ShowTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
EAPPasswordBox passwordBox = sender as EAPPasswordBox;
if (passwordBox != null)
{
passwordBox.SetVisibilityOfTextBoxes();
}
}
Hope it helps.
Implement what you do in the setter of the property in the OnShowTextPropertyChanged handler. The setter will only be used to initialise the binding.