PropertyChanged event always null even after setting DataContext - wpf

I have a Model with INotifyPropertyChanged handling copied from tutorials:
public event PropertyChangedEventHandler? PropertyChanged;
protected void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I update a member of the class, I call the handler:
public string? Id
{
get => _id;
set
{
if (value != _id)
{
_id = value;
Notify(nameof(Id));
}
}
}
And in the view code behind I have:
private Goal _goal;
public GoalControl()
{
InitializeComponent();
this._goal = new MyGoal();
this.DataContext = _goal;
Binding binding = new Binding("Text");
binding.Source = _goal.Id;
binding.Mode = BindingMode.TwoWay;
_ = Id.SetBinding(TextBox.TextProperty, binding);
}
But the view doesn't pick up any changes to the field. When I debug, I find that PropertyChanged is always null. How should I set it to a useful value?
This is in a user control, by the way, which will be generated dynamically so I don't think I can do the binding from XAML.

Assuming that Id is a TextBox in your GoalControl, you would bind its Text property to the Id of the MyGoal object in the DataContext like shown below.
You do not set the Source property of the Binding, because the source object should be provided by the current DataContext. Also note that TwoWay is the default binding mode for the TextBox.Text property and does not need to be set explicitly.
public GoalControl()
{
InitializeComponent();
_goal = new MyGoal();
DataContext = _goal;
Binding binding = new Binding("Id");
Id.SetBinding(TextBox.TextProperty, binding);
}
The Binding could as well be written in XAML:
<TextBox Text="{Binding Id}"/>
Since this is in a UserControl, you should however not set the DataContext at all. UserControls, as any other controls, should not have "private" view models like your MyGoal object.
The UserControl would instead expose a dependency property Id, which is bound when you use the control, like
<local:GoalControl Id="{Binding SomeViewModelId}"/>
In the UserControl's XAML, the Binding to the own property would specify the Source object as RelativeSource:
<TextBox Text="{Binding Id,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>

Related

WPF Custom Control Dependency Property setter not getting called?

I have created a custom control CustomTextBox inherited from TextBox class. I have created a dependency property named CustomTextProperty.
I have binded this DP with my Viewmodel property.
While Registering the DP i have given the property change callback but it is only get called one time when my control gets the binded data initially when my xaml loads.
When i try to set my control from view the binded VM property setter does not gets called and also the propertychangecallback not gets fired.
Please help!!
Code snipet below:
My Custom control
class CustomTextBox : TextBox
{
public static readonly DependencyProperty CustomTextProperty = DependencyProperty.Register("CustomText",
typeof(string), typeof(CustomTextBox),
new FrameworkPropertyMetadata("CustomTextBox",
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnCustomPropertyChange)));
public string CustomText
{
get { return (string)GetValue(CustomTextProperty); }
set { SetValue(CustomTextProperty, value); }
}
private static void OnCustomPropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This is Demo Application.
// Code to be done Later...
}
}
My View Model:
public class ViewModel : INotifyPropertyChanged
{
private string textForTextBox;
public string TextForCustomTextBox
{
get
{
return this.textForTextBox;
}
set
{
this.textForTextBox = value;
this.OnPropertyChange("TextForCustomTextBox");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
My Xaml Code with Binding:
<custom:CustomTextBox x:Name="CustomTextBox"
CustomText="{Binding TextForCustomTextBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" HorizontalAlignment="Center" Width="200" Height="50" />
My Code Behind to set DataContext:
// My View Constructor
public View1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
You said that you declared a CustomText DependencyProperty and data bound it to your view model TextForCustomTextBox property and that much is correct. However, when you said that you tried to set your property from the view, you were mistaken.
What you were actually doing was setting the CustomTextBox .Text property from the view and that wasn't connected to your CustomTextBox.CustomText property. You can connect them like this, although I'm not quite sure what the point of that would be:
<Views:CustomTextBox x:Name="CustomTextBox" Text="{Binding CustomText, RelativeSource={
RelativeSource Self}, UpdateSourceTrigger=PropertyChanged}" CustomText="{Binding
TextForCustomTextBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" HorizontalAlignment="Center" Width="200" Height="50" />
Try setting your DataContext BEFORE the actual initialization so it is available when the form/control objects are created. If it can't find before, is that what may be causing the failed bindings.

Setting WPF datacontext for a specific control

I'm developing a WPF application and I'm struggling a little bit to understand some of the details of DataContext as it applies to binding. My application uses a business object which is defined like this:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// enumerations for some properties
public enum MyEnumValues
{
[Description("New York")]
NewYork,
[Description("Chicago")]
Chicago,
[Description("Los Angeles")]
LosAngeles
}
// an example property
private string _myPropertyName;
public string MyPropertyName
{
get { return _myPropertyName; }
set
{
if (_myPropertyName == value)
{
return;
}
_myPropertyName = value;
OnPropertyChanged(new PropertyChangedEventArgs("MyPropertyName"));
}
}
// another example property
private MyEnumValues _myEnumPropertyName;
public MyEnumValues MyEnumPropertyName
{
get { return _myEnumPropertyName; }
set
{
if (_myEnumPropertyName== value)
{
return;
}
_myEnumPropertyName= value;
OnPropertyChanged(new PropertyChangedEventArgs("MyEnumPropertyName"));
}
}
// example list property of type Widget
public List<Widget> MyWidgets { get; set; }
// constructor
public MyBusinessObject()
{
// initialize list of widgets
MyWidgets = new List<Widget>();
// add 10 widgets to the list
for (int i = 1; i <= 10; i++)
{
MyWidgets.Add(new Widget());
}
// set default settings
this.MyPropertyName = string.empty;
}
}
As you can see, I have some properties that are declared in this class one of which is a list of Widgets. The Widget class itself also implements INotifyPropertyChanged and exposes about 30 properties.
My UI has a combobox which is bound to my list of Widgets like this:
MyBusinessObject myBusinessObject = new MyBusinessObject();
public MainWindow()
{
InitializeComponent();
this.DataContext = myBusinessObject;
selectedWidgetComboBox.ItemsSource = myBusinessObject.MyWidgets;
selectedWidgetComboBox.DisplayMemberPath = "WidgetName";
selectedWidgetComboBox.SelectedValuePath = "WidgetName";
}
The majority of the controls on my UI are used to display the properties of a Widget. When my user selects a Widget from the combobox, I want these controls to display the properties for the selected Widget. I'm currently achieving this behavior by updating my window's DataContext in the SelectionChanged event handler of my combobox like this:
private void selectedWidgetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.DataContext = selectedWidgetComboBox.SelectedItem;
}
This allows me to bind my controls to the appropriate Widget property like this:
<TextBox Text="{Binding WidgetColor}"></TextBox>
However, not all of the controls in my UI are used to display Widget properties. Some of the controls need to display the properties from MyBusinessObject (for example: MyPropertyName defined above). In this scenario, I can't simply say:
<TextBox Text="{Binding MyPropertyName}"></TextBox>
...because the DataContext of the window is pointing to the selected Widget instead of MyBusinessObject. Can anyone tell me how I set the DataContext for a specific control (in XAML) to reference the fact that MyPropertyName is a property of MyBusinessObject? Thank you!
Instead of changing the DataContext of your window, you should add a property to your MyBusinessObject class like this one:
private Widget _selectedWidget;
public Widget SelectedWidget
{
get { return _selectedWidget; }
set
{
if (_selectedWidget == value)
{
return;
}
_selectedWidget = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedWidget"));
}
}
Then bind SelectedWidget to the SelectedItem property of your combobox. Anywhere that you need to use the widget's properties you can do this:
<TextBox Text="{Binding Path=SelectedWidget.WidgetColor}"></TextBox>
try
<TextBox Text="{Binding MyBusinessObject.MyPropertyName}"></TextBox>
this works if MyBusinessObject is the datacontext of the textbox and MyPropertyName is a property of MyBusinessObject
Also, Here is a good article to clarify binding
hope this helps
EDIT 1:
use a relative binding like this:
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfControl}}}"
So the relatve binding allows you to look up the visual tree to another UI element and use its datacontext. I would consider wrapping your window's contents in a grid. and wet your windows datacontext to the businessobject and the grids datacontext to the widget. That way you can always use the parent window's datacontext through the realtive source binding.
so use the following if your window's datacontext is your business object
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"

WPF MVVM TreeView SelectedItem

This cannot be this difficult. The TreeView in WPF doesn't allow you to set the SelectedItem, saying that the property is ReadOnly. I have the TreeView populating, even updating when it's databound collection changes.
I just need to know what item is selected. I am using MVVM, so there is no codebehind or variable to reference the treeview by. This is the only solution I have found, but it is an obvious hack, it creates another element in XAML that uses ElementName binding to set itself to the treeviews selected item, which you must then bind your Viewmodel too. Several other questions are asked about this, but no other working solutions are given.
I have seen this question, but using the answer given gives me compile errors, for some reason I cannot add a reference to the blend sdk System.Windows.Interactivity to my project. It says "unknown error system.windows has not been preloaded" and I haven't yet figured out how to get past that.
For Bonus Points: why the hell did Microsoft make this element's SelectedItem property ReadOnly?
You should not really need to deal with the SelectedItem property directly, bind IsSelected to a property on your viewmodel and keep track of the selected item there.
A sketch:
<TreeView ItemsSource="{Binding TreeData}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
public class TViewModel : INotifyPropertyChanged
{
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which
// also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree,
// otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
static virtual void OnSelectedItemChanged()
{
// Raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
A very unusual but quite effective way to solve this in a MVVM-acceptable way is the following:
Create a visibility-collapsed ContentControl on the same View the TreeView is. Name it appropriately, and bind its Content to some SelectedSomething property in viewmodel. This ContentControl will "hold" the selected object and handle it's binding, OneWayToSource;
Listen to the SelectedItemChanged in TreeView, and add a handler in code-behind to set your ContentControl.Content to the newly selected item.
XAML:
<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
SelectedItemChanged="TreeView_SelectedItemChanged">
Code Behind:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemHelper.Content = e.NewValue;
}
ViewModel:
public object SelectedObject // Class is not actually "object"
{
get { return _selected_object; }
set
{
_selected_object = value;
RaisePropertyChanged(() => SelectedObject);
Console.WriteLine(SelectedObject);
}
}
object _selected_object;
You can create an attached property that is bindable and has a getter and setter:
public class TreeViewHelper
{
private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is TreeView))
return;
if (!behaviors.ContainsKey(obj))
behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));
TreeViewSelectedItemBehavior view = behaviors[obj];
view.ChangeSelectedItem(e.NewValue);
}
private class TreeViewSelectedItemBehavior
{
TreeView view;
public TreeViewSelectedItemBehavior(TreeView view)
{
this.view = view;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
}
internal void ChangeSelectedItem(object p)
{
TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
item.IsSelected = true;
}
}
}
Add the namespace declaration containing that class to your XAML and bind as follows (local is how I named the namespace declaration):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
Now you can bind the selected item, and also set it in your view model to change it programmatically, should that requirement ever arise. This is, of course, assuming that you implement INotifyPropertyChanged on that particular property.
Use the OneWayToSource binding mode. This doesn't work. See edit.
Edit: Looks like this is a bug or "by design" behavior from Microsoft, according to this question; there are some workarounds posted, though. Do any of those work for your TreeView?
The Microsoft Connect issue: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Posted by Microsoft on 1/10/2010 at 2:46 PM
We cannot do this in WPF today, for the same reason we cannot support
bindings on properties that are not DependencyProperties. The runtime
per-instance state of a binding is held in a BindingExpression, which
we store in the EffectiveValueTable for the target DependencyObject.
When the target property is not a DP or the DP is read-only, there's
no place to store the BindingExpression.
It's possible we may some day choose to extend binding functionality
to these two scenarios. We get asked about them pretty frequently. In
other words, your request is already on our list of features to
consider in future releases.
Thanks for your feedback.
I decided to use a combination of code behind and viewmodel code. the xaml is like this:
<TreeView
Name="tvCountries"
ItemsSource="{Binding Path=Countries}"
ItemTemplate="{StaticResource ResourceKey=countryTemplate}"
SelectedValuePath="Name"
SelectedItemChanged="tvCountries_SelectedItemChanged">
Code behind
private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
if (vm != null)
{
var treeItem = sender as TreeView;
vm.TreeItemSelected = treeItem.SelectedItem;
}
}
And in the viewmodel there is a TreeItemSelected object which you can then access in the viewmodel.
You can always create a DependencyProperty that uses ICommand and listen to the SelectedItemChanged event on the TreeView. This can be a bit easier than binding IsSelected, but I imagine you will wind up binding IsSelected anyway for other reasons. If you just want to bind on IsSelected you can always have your item send a message whenever IsSelected changes. Then you can listen to those messages anyplace in your program.

WPF- Is there a way to bind to the SelectedValues of both a TreeView and a ListBox?

I need to bind so that the Content of a content control is set to the SelectedValue of either the TreeView or the ListBox. The SelectedValue that was most recently changed should provide the content for the ContentControl.
I was able to get this working using the following concept.
Bind the content control to a read only property "SelectedItem" (with private property _selectedItem).
Bind the ListBox.SelectedItem to a read/write property "SelectedItemLB".
In the SelectedItemLB setter, set the value of _selectedItem, and raise the PropertyChanged event for SelectedItem.
Create a handler for VreeView.SelectedItemChanged, which sets the value of _selectedItem and raises the PropertyChanged event for SelectedItem.
Here is my full code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.items = new List<object>();
this.items.Add(new Car("Green"));
this.items.Add(new Car("Blue"));
this.items.Add(new Car("Red"));
this._selectedItem = this.items[0];
this.treeView1.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(treeView1_SelectedItemChanged);
this.DataContext = this;
}
void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this._selectedItem = treeView1.SelectedItem;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
private List<object> items;
public List<object> Items
{
get { return items; }
set { items = value; }
}
public object SelectedItemLB
{
get { return _selectedItem; }
set
{
_selectedItem = value;
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
The XAML is pretty simple:
<StackPanel>
<ListBox Name="listBox1" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItemLB, Mode=TwoWay}" ></ListBox>
<TreeView Name="treeView1" ItemsSource="{Binding Path=Items}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}"></Setter>
</Style>
</TreeView.Resources>
</TreeView>
<ContentControl Content="{Binding Path=SelectedItem.Color}"></ContentControl>
</StackPanel>
I can't think of a way to do that directly. However there are several straightforward solutions.
A. Use events to set the Content
Simply attach a common handler to the SelectedValueChanged events of your ItemsControls. Whenever one of them changes its selection, the handler will set the Content to whatever was selected. I think this is most simple.
B. Use intermediary properties
Bind the SelectedValue of each ItemsControl to a property. In the property's setter, also set the Content equal to value. This allows you to use data binding instead of event handlers, but it still requires you to write code-behind and it doesn't buy you much. Of course, if you are already binding to properties for other purposes, there is almost no extra cost (only an assignment in each setter) so this method might be preferable.

WPF ObservableCollection in xaml

I have created an ObservableCollection in the code behind of a user control. It is created when the window loads:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Entities db = new Entities();
ObservableCollection<Image> _imageCollection =
new ObservableCollection<Image>();
IEnumerable<library> libraryQuery =
from c in db.ElectricalLibraries
select c;
foreach (ElectricalLibrary c in libraryQuery)
{
Image finalImage = new Image();
finalImage.Width = 80;
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri(c.url);
logo.EndInit();
finalImage.Source = logo;
_imageCollection.Add(finalImage);
}
}
I need to get the ObservableCollection of images which are created based on the url saved in a database. But I need a ListView or other ItemsControl to bind to it in XAML file like this:
But I can't figure it out how to pass the ObservableCollection to the ItemsSource of that control. I tried to create a class and then create an instance of a class in xaml file but it did not work. Should I create a static resource somehow>
Any help will be greatly appreciated.
Firstly, the ObservableCollection is a local variable. What you need to do is have it as a private global variable and expose it with a public property. You can use the INotifyPropertyChanged interface to have the image data update automagically when the actual collection itself changes.
In your XAML, you then need to set the DataContext to self, and you can then directly bind your public property to the ItemsSource. You may want to use an ItemTemplate for displaying the items in a custom manner.
Cheers,
Adam
Example as requested:
In C#:
public MyWindowClass
{
public ObservableCollection<image> MyImageCollection
{
get;
set;
}
}
In XAML:
<UserControl
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
...
<ListBox ItemsSource="{Binding MyImageCollection}" ItemTemplate="*yourtemplateresource*" />
...
</UserControl>
Now, the reason that I mentioned using INotifyPropertyChanged is that if you try:
MyImageCollection = new ObservableCollection<image>();
The items in the listbox will not automatically update. With an ObservableCollection, however, you do not need to implement INotifyPropertyChanged for basic addition and removal of list items.
You have to set the DataContext of the UserControl to your collection:
DataContext = _imageCollection
You can do that in the UserControl_Loaded() method.
Next you need to bind the ItemsSource of the ListView in the XAML:
<ListView ItemsSource="{Binding}"/>
The {Binding} is equivalent to {Binding .} which binds to the DataContext of the UserControl. If you need "more stuff" in your DataContext you can instead create a class like this:
class ViewModel : INotifyPropertyChanged {
public ObservableCollection Images { get { ... } }
...
}
Use this class for the DataContext:
DataContext = new ViewModel();
And replace the binding to bind to the Images property:
<ListView ItemsSource="{Binding Images}"/>
Then you can add another property to ViewModel:
class ViewModel : INotifyPropertyChanged {
public ObservableCollection Images { get { ... } }
public String Message { get { ... } set { ... } }
...
}
And bind it to a control:
<TextBlock Text="{Binding Message}"/>
Remember to fire the PropertyChanged event when the Message property is changed in ViewModel. This will update the UI when view-model properties are changed by code.

Resources