DataBinding to a static singleton - wpf

I have a problem binding to a WPF form . I have my own static "settings" class (singleton) that implements PropertyChangedEventHandler and raises the event whenever a property is updated.
The singleton object is added to resources in the form's constructor and the property is correctly read on form's initialization, thus suggesting that the binding is correct.
However, WPF does NOT register any event handler for PropertyChangedEventHandler and PropertyChanged is always null. Thus the event is never raised, and my form is never updated (it's meant to be updated on a button click).
What am I doing wrong?
I suspect that calling Resources.Add for some reason prevents WPF from registering its own event handler, but I'm not sure.
I've read multiple SO questions on similar topics, but the 2 most common issues are not creating a proper singleton (thus passing another instance to xaml then intended) or not implementing INotifyPropertyChanged. I'm doing both of these correctly.
Expected behavior:
Settings.TextValue is the property I'm interested in. In its setter, NotifyPropertyChanged is called, which unfortunately fails to raise this.PropertyChanged event, since WPF registers no handler.
When MainWindow.Button1 is click, the textBox's value is supposed to change to "ButtonA OK" from the initial value of Settings.TextBox ("testOK").
Here's the code:
Settings.cs:
namespace bindings
{
public sealed class Settings : INotifyPropertyChanged
{
private static readonly Settings instance = new Settings();
private Settings()
{
}
public static Settings Instance { get { return instance; } }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = null)
{
// passing propertyName=null raises the event for all properties
if (PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string textValue = "testOK";
public static string TextValue
{
get { return Instance.textValue; }
set { Instance.textValue = value; Instance.NotifyPropertyChanged(); }
}
}
MainWindow.xaml.cs
namespace bindings
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Resources.Add("foobar", Settings.Instance);
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void button1_Click(object sender, RoutedEventArgs e)
{
int hash = Settings.Instance.GetHashCode();
Settings.TextValue = "ButtonA OK";
}
}
}
MainWindow.xaml
<Window x:Class="bindings.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" WindowStyle="ToolWindow">
<Grid PresentationTraceSources.TraceLevel="High" DataContext="{StaticResource foobar}">
<Button Content="ButtonA" Height="33" HorizontalAlignment="Left" Margin="76,243,0,0" Name="button1" VerticalAlignment="Top" Width="101" Click="button1_Click" />
<TextBox Height="28" HorizontalAlignment="Left" Margin="182,180,0,0" Name="textBox1" VerticalAlignment="Top" Width="93"
Text="{Binding Path=TextValue, Mode=OneWay}" DataContext="{Binding}" PresentationTraceSources.TraceLevel="High"/>
</Grid>
</Window>
Thanks for help!

Related

wpf Button always disabled (with CommandBinding, CanExecute=True and IsEnabled= True)

Revised: I apologize for missing some important descriptions in the first version, now the problem should be well-defined:
so I'm making a toy CAD program with following views:
MainWindow.xaml
CustomizedUserControl.xaml
CustomizedUserControl is a Tab within MainWindow, and its DataContext is defined in MainWindow.xaml as:
<Window.Resources>
<DataTemplate DataType="{x:Type local:CustomizedTabClass}">
<local:UserControl1/>
</DataTemplate>
</Window.Resources>
And CustomizedUserControl.xaml provides a canvas and a button, so when the button is pressed the user should be able to draw on the canvas. As the following code shows, the content of Canvas is prepared by the dataContext, "tabs:CustomizedTabClass".
CustomizedUserControl.xaml
<CustomizedUserControl x:Name="Views.CustomizedUserControl11"
...
>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
...
<canvas x:Name="CADCanvas"
Drawing="{Binding Drawing ,Mode=TwoWay}" >
</canvas>
It is also notable that I used an external library, Fody/PropertyChanged, in all classes so property notifications would be injected without further programming.
CustomizedUserControl.xaml.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
[AddINotifyPropertyChangedInterface]
public partial class CustomizedUserControl: Usercontrol, INotifyPropertyChanged{
public CADDrawingCommands DrawingCommands { get; set; }
public CustomizedUserControl()
{
InitializeComponent();
DrawingCommands = new CADDrawingCommands(this);
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
CADDrawingCommands.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows;
[AddINotifyPropertyChangedInterface]
public class CADDrawingCommands : INotifyPropertyChanged{
UserControl _drawableTab;
public string Button1Name { get; set; } = "TestForDataBinding";
public RoutedCommand LinesChainCommand { get; set; } = new RoutedCommand();
public CADDrawingCommands(UserControl dTab){
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
The Content of Button is set correctly, as I can read the string defined in Button1Name:
Therefore I suppose the Data Binding for Command is also ok. IsEnabled has been set to true and CanExecute of the CommandBinding would only return true.
Why is my button still greyed out and not clickable?
If I define the button inside a Window instead of UserControl (and set the datacontext of the Window to its own code behind, the button will be clickable! Why?
Thank you for your time! Hopefully would somebody help me cuz I've run out of ideas and references.
Made the simplest example.
Everything works as it should.
BaseInpc is my simple INotifyPropertyChanged implementation from here: BaseInpc
using Simplified;
using System.Windows;
using System.Windows.Input;
namespace CustomizedUserControlRoutedCommand
{
public class CADDrawingCommands : BaseInpc
{
UIElement _drawableTab;
private string _button1Name = "TestForDataBinding";
public string Button1Name { get => _button1Name; set => Set(ref _button1Name, value); }
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
public CADDrawingCommands(UIElement dTab)
{
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
}
}
<UserControl x:Name="CustomizedUserControl11" x:Class="CustomizedUserControlRoutedCommand.CustomizedUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:CADDrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
</Grid>
</UserControl>
using System.Windows.Controls;
namespace CustomizedUserControlRoutedCommand
{
public partial class CustomizedUserControl : UserControl
{
public CADDrawingCommands DrawingCommands { get; }
public CustomizedUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
}
}
<Window x:Class="CustomizedUserControlRoutedCommand.TestCustomizedUserControlWindow"
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"
xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
Title="TestCustomizedUserControlWindow" Height="450" Width="800">
<Grid>
<local:CustomizedUserControl/>
</Grid>
</Window>
If you showed your code in full, then I see the following problems in it:
You are setting the value incorrectly for the DrawingCommands property.
In this property, you do not raise PropertyChanged.
The binding in the Button is initialized in the InitializeComponent() method. At this point, the property is empty, and when you set a value to it, the binding cannot find out.
There are two ways to fix this:
Raise PropertyChanged in the property;
If you set the property value once in the constructor, then set it immediately in the initializer. Make the property "Read Only". This way, in my opinion, is better.
public CADDrawingCommands DrawingCommands { get; }
public FileEditTabUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
You have a button bound to a command in the DrawingCommands.LinesChainCommand property.
But to this property, you assign an empty instance of the = new RoutedCommand () routing command.
This looks pointless enough.
If you need a routable command, create it in the "Read Only" static property.
This will make it much easier to use in XAML:
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
Raising PropertyChanged in CADDrawingCommands properties is also not visible in your code.
If it really does not exist, then the binding is also unaware of changing property values.

Can you have an event setter bound to an event of the data context?

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.

Why ListBox does not display the bound ItemsSource

I am new to WPF. I have created a WPF project, and add the following class
public class MessageList:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private List<string> list = new List<string>();
public List<string> MsgList
{
get { return list; }
set
{
list = value;
OnPropertyChanged("MsgList");
}
}
public void AddItem(string item)
{
this.MsgList.Add(item);
OnPropertyChanged("MsgList");
}
}
Then in the main window I added a ListBox and below is the xaml content
<Window.DataContext>
<ObjectDataProvider x:Name="dataSource" ObjectType="{x:Type src:MessageList}"/>
</Window.DataContext>
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="52,44,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<ListBox Height="233" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" Margin="185,44,0,0" Name="listBox1" VerticalAlignment="Top" Width="260" ItemsSource="{Binding Path=MsgList}" />
</Grid>
Here is the source code of MainWindow.cs
public partial class MainWindow : Window
{
private MessageList mlist = null;
public MainWindow()
{
InitializeComponent();
object obj = this.DataContext;
if (obj is ObjectDataProvider)
{
this.mlist = ((ObjectDataProvider)obj).ObjectInstance as MessageList;
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.mlist.AddItem(DateTime.Now.ToString());
}
}
My question is after I clicked the button, there isn't any content displayed on the Listbox, what is the reason?
You should use an ObservableCollection instead of a List to notify the UI of collection changes.
You asked for a reason, while devdigital gave you the solution its worth mentioning why it is not working, and why his fix works:
Your mlist is bound to the ListBox and its all working well. Now you press the button and you add an entry to your list. The listbox just won't know about this change, because your list has no way of telling "Hey i just added a new item". To do that, you need to use a Collection implementing INotifyCollectionChanged, like the ObservableCollection. This is very similar to your OnPropertyChanged if you modify a property on your MessageList it also calls the OnPropertychanged method which fires the PropertyChanged event. The Databinding registers to the PropertyChanged event and now knows when you updated your property and automatically updates the UI. The same is necessary for Collections if you want this automatic updating of the UI on collections.
The culprit is the string items... string items being of primitive type, do not refresh bindings on the list box when you do the OnPropertyChanged
Either use observable collection or call this in your button1_Click() function...
listBox1.Items.Refresh();

How do I manually refresh a databinding?

I have big query based datamodel, and I wish to display results of Linq queries into grids.
The GUI will edit attributes, which will affect the query result. However, even though the binding executes just fine, the debugger shows no subscriber to the PropertyChanged event (it is "null"). I have made this test example.
I wish for the user to set a bunch of criteria and then hit an "execute" button. In my example, I expected the number of items in the grid to change.
Here is the xaml:
<Window x:Class="GridViewNotifyTest.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>
<StackPanel>
<Button Click="Button_Click">Take 3</Button>
<Button Click="Button_Click_1">Take 5</Button>
<Button Click="Button_Click_2">FireNotify</Button>
<DataGrid ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
</Window>
And here is the C#:
namespace GridViewNotifyTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window,INotifyPropertyChanged
{
private int _takeAmount;
public MainWindow()
{
InitializeComponent();
_takeAmount = 4;
DataContext = Amount;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_takeAmount = 3;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
_takeAmount = 5;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
OnPropertyValueChanged("Amount");
}
protected virtual void OnPropertyValueChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); // THE DEBUGGER SHOWS THE PROPERTYCHANGED DELEGATE AS NULL.
}
public IEnumerable<int> Amount
{
get { return Enumerable.Range(1,10).Take(_takeAmount); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
According to question title, fast answer will be to use BindingExpression.UpdateTarget method.
Set the DataContext to this and then change your Binding to be
<DataGrid ItemsSource="{Binding Amount}">

WPF Binding with INotifyPropertyChanged does not update

I appear to be having serious problems getting my WPF UI to update when I update when I update the property it is bound to. Here is my view model class definition:
namespace WpfModel
{
class AppModel : INotifyPropertyChanged
{
private int _count = 7;
public int Count { get { return _count; }
set { _count = value; OnPropertyChanged("Count"); } }
public void Increment()
{
Count++;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs("prop");
handler(this, e);
}
}
};
}
This is bound to my simple UI in the following XAML:
<Window x:Class="WpfModel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfModel"
Title="WPF Data Model Demo" Height="128" Width="284" >
<Window.DataContext>
<vm:AppModel />
</Window.DataContext>
<Grid Height="Auto" Width="Auto">
<Button Margin="0,0,12,12" Name="IncButton" Height="23" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="75" Click="IncButton_Click" Content="Increment" />
<Label Content="Count Variable:" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
<Label Height="28" Margin="116,12,0,0" Name="CountLabel" VerticalAlignment="Top" HorizontalAlignment="Left" Width="40" Content="{Binding Path=Count}" />
</Grid>
</Window>
The application is defined like follows:
namespace WpfModel
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private AppModel _model = new AppModel();
public MainWindow()
{
InitializeComponent();
//_model = new AppModel();
}
private void IncButton_Click(object sender, RoutedEventArgs e)
{
}
}
}
When the app starts up, everything is peachy, the label even initially displays whatever value is in the Count property of the model. Calling increment updates the model properly, and directly incrementing the Count property as shown in the code also works just fine.
The problem here, is that the PropertyChanged event handler never seems to get added to. I can step through the code in the debugger and see the value in the property updating, and I can see the call to OnPropertyChanged even, but the PropertyChanged handler itself is always null.
Is there a problem with my binding maybe?
I am using MSVC 2010 Express.
The issue is that the WpfModel you have as an instance variable, _model, is not the same instance that's being used as the Window's DataContext. Thus, it is not bound to by anything, and its PropertyChanged will always be null.
Change your XAML for setting the datacontext to this:
<Window.DataContext>
<vm:AppModel x:Name="_model" />
</Window.DataContext>
Get rid of the instance variable declared in the code behind, and fix the OnPropertyChanged implementation (use the parameter instead of the literal string "prop"), and it works.
this line:
var e = new PropertyChangedEventArgs("prop");
should be:
var e = new PropertyChangedEventArgs(prop);
You are passing the string "prop" so the bound values listening for "Count" will not be triggered.
**EDIT **
Based on your other comments (Answer?) I thought I had better update this with a fix - you could use the answer already given of course but I dont want to copy someone else :).
Add this to your click handler:
((AppModel)this.DataContext).Count += 1;

Resources