WPF binding in ItemTemplate works but produces error messages - c#

I am using Mahapps.Metro.Controls.DropDownButton in a UserControl in my project, which I populate using data binding. In order to know which item gets selected, I apply an item template in which I specify the item click handler. The relevant XAML is
<Controls:DropDownButton
x:Name="selector"
VerticalContentAlignment="Center"
Content=" "
Background="Transparent"
BorderThickness="0"
ItemsSource="{Binding Catalogues}"
>
<Controls:DropDownButton.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}" MouseDown="HandleDropDownItemMouseDown" />
</DataTemplate>
</Controls:DropDownButton.ItemTemplate>
</Controls:DropDownButton>
The DataContext is a custom view model set in the constructor in the code-behind for the user control:
public CatalogueEditor()
{
InitializeComponent();
this.viewModel = new CatalogueEditorViewModel();
this.DataContext = this.viewModel;
}
The Catalogues property in the view model is a custom KeyedCollection<string, Catalogue> that implements INotifyCollectionChanged. This contains elements that are similar custom KeyedCollection objects implementing INotifyCollectionChanged, but with item type Question, which is no longer a collection. Catalogue objects have a read-only property Id, to which I bind the TextBlock in the item template.
The binding seems to work all right and the DropDownButton gets populated with the Id labels of the Catalogue objects in the Catalogues collection, yet I get an output informing me of a binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'Id' property not found on 'object' ''String' (HashCode=-842352768)'. BindingExpression:Path=Id; DataItem='String' (HashCode=-842352768); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
This tells me that at some point the DataContext of the TextBlock in the item template is perceived as a String, though I intend it to be a Catalogue as an item in the collection bound to the ItemsSource. It is not only the correct operation that corroborates this notion but also my HandleDropDownItemMouseDown event handler:
void HandleDropDownItemMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left && selector.IsExpanded) {
Catalogue catalogue = ((TextBlock)e.Source).DataContext as Catalogue;
if (catalogue != null) {
viewModel.Select(catalogue);
}
}
}
Placing a breakpoint here I can see that the DataContext of the TextBlock is indeed a Catalogue and the code works as intended.
Why does this apparent error message occur? Shall I worry about it, does it signify that I made some insidious mistake in my code, or shall I be content that the code works? My apologies if this is an irrelevant or stupid question, but I am just learning WPF and I find it quite challenging, so I try to understand what happens around me even if my code happens to work. Your insights are much appreciated.

The offending line is Content=" ". You are setting content of a control to " " string, to which control tries to apply your template. As string class has no Id property, it results in a binding error.

Related

Simple TextBox Text Binding Fails WPF

We are converting an app from Silverlight to WPF. It's a fairly complex app, but the code sharing is about 95% +. The XAML is pretty much all the same except for XML namespace definitions etc. About 90% of the app now works but there are a few glaring issues that are puzzling me. One is this binding issue.
We have a model object called TaskInfo. It has a property called TaskNo. in Silverlight and WPF we bind to this property like this
<TextBox IsReadOnly="True" Grid.Column="0" Grid.Row="0" Margin="1" Text="{Binding Path=TaskNo}" Height="28" Background="#CAECF4" VerticalAlignment="Center" VerticalContentAlignment="Center" />
In both WPF and Silverlight the TaskNo is correctly displayed when the TaskInfo model is first set as the DataContext. In Silverlight, if we create a new TaskInfo, send it to the server for saving, and return the model with a new TaskNo, the TaskNo is successfully displayed. But, in WPF, it just displays 0 when the saved TaskInfo is returned from the server. There is some issue with binding. This is the binding error I see in the output window:
System.Windows.Data Information: 10 : Cannot retrieve value using the
binding and no valid fallback value exists; using default instead.
BindingExpression:Path=TaskNo; DataItem=null; target element is
'TextBlock' (Name=''); target property is 'Text' (type 'String')
I inspected the visual tree and the TextBox's DataContext is set to the TaskInfo as expected.
So, I turned off binding and tried this code. It's the event handler for the DataContextChanging on the TextBox. This code works fine. When a new task is saved and returned, the TaskNo successfully displays here:
private void TaskNoBox_DataContextChanging(object sender, DependencyPropertyChangedEventArgs e)
{
var task = TaskNoBox.DataContext as TaskInfo;
if (task == null)
{
throw new Exception("Ouch!");
}
TaskNoBox.Text = task.TaskNo.ToString();
}
To further debug this problem, I added this event handler for the GotFocus event on the text box. So, after the task has been saved on the server side and has been returned and set as the DataContext, I click inside the control to fire this event handler. When I step through this code, I can see that the DataContext is correct, and has the correct TaskNo. Calling this code still doesn't cause the binding to occur.
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
var be = textBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
be.UpdateTarget();
}
TextBox Text binding property:
DataContext of TextBox's properties:
How do I make sense of this binding error? What are the binding gotchas between Silverlight and WPF? Do I need some kind of workaround? Why is binding not working?
Binding in WPF never updates if the previous DataContext is equivalent to the new DataContext according to the Equals method.
The difference between Silverlight and WPF seems to be that when the DataContext changes, WPF seems to use the Equals method to evaluate difference between objects while Silverlight uses the reference. That means that WPF is the same as Xamarin.Forms.
I tried this code, and it causes the TaskNo to display correctly. I think what is happening is that because the previous DataContext was equivalent to the new DataContext when Equals is called. So, this works around the problem.
private async void TaskPageHeader_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TaskNoBox.DataContext = new object();
TaskNoBox.DataContext = CurrentTask;
}

WPF Binding ListBox to ViewModel Collection without binding DataContext directly to the collection [duplicate]

I'm banging my head on my desk with this binding error.. I have checked several of the postings for the BindingExpression path error and cannot see anything that works with my situation.
Anyway, I have a custom control called IncrementingTextBox. I am trying to disable it whenever the user 'checks' the CheckBox above it.
I have a binding on the CheckBox IsChecked property that is working fine and is firing when it is supposed to. It is correctly setting the UseSensorLength property on the ConfigurationModel.
However, the binding on the IncrementingTextBox IsEnabled property is causing a BindingExpression path error and so doesn't update at all.
As a test, I tried in the code behind to enable and disable the control and it works just fine, but I can't seem to get the Binding to work on it.
Here is a snippet from my xaml:
...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
...
...
<CheckBox Content="Use Sensor Length" Margin="30,6,0,0"
IsChecked="{Binding ConfigurationModel.UseSensorLength, Mode=TwoWay}"/>
<local:IncrementingTextBox x:Name="video_length_textbox" Margin="0,0,0,5"
IsTextEnabled="False"
IsEnabled="{Binding ConfigurationModel.DontUseSensorLength}"
ValueChanged="VideoEventValueChanged"/>
And Here is a snippet from my ConfigurationModel:
public bool DontUseSensorLength
{
get { return !UseSensorLength; }
}
public bool UseSensorLength
{
get { return _useSensorLength; }
set
{
_useSensorLength = value;
OnPropertyChanged("UseSensorLength");
OnPropertyChanged("DontUseSensorLength");
}
}
Here is the error message I am getting in my output window when running the app:
System.Windows.Data Error: 40 : BindingExpression path error:
'ConfigurationModel' property not found on 'object'
''IncrementingTextBox' (Name='video_length_textbox')'.
BindingExpression:Path=ConfigurationModel.DontUseSensorLength;
DataItem='IncrementingTextBox' (Name='video_length_textbox'); target
element is 'IncrementingTextBox' (Name='video_length_textbox'); target
property is 'IsEnabled' (type 'Boolean')
Remember, the 'UseSensorLength' property binding is working fine, but the 'DontUseSensorLength' binding is causing the above 'BindingExpression path error'.
I wrote some other SO answer recently about how to read the binding errors so they make more sense. To summarize, add line breaks to your error message on the colons and semi-colons, and read it from the bottom up.
Your error message is:
System.Windows.Data Error: 40 :
BindingExpression path error: 'ConfigurationModel' property not found on 'object' ''IncrementingTextBox' (Name='video_length_textbox')'.
BindingExpression:Path=ConfigurationModel.DontUseSensorLength;
DataItem='IncrementingTextBox' (Name='video_length_textbox');
target element is 'IncrementingTextBox' (Name='video_length_textbox');
target property is 'IsEnabled' (type 'Boolean')
This can be read from the bottom up as:
The binding failing is the IsEnabled property of an element of type IncrementingTextBox (named video_length_textbox).
The DataItem (DataContext) of the element is an object of type IncrementingTextBox named video_length_textbox
The binding expression it is trying to find is ConfigurationModel.DontUseSensorLength
And the problem the binding is having is that the ConfigurationModel property is not found on the data context object IncrementingTextBox
So your DataContext for "video_length_textbox" is set to itself, and your IncrementingTextBox class does not have a public property called ConfigurationModel
Since I don't see you setting the DataContext for your IncrementingTextBox anywhere in your XAML, check out the code for your IncrementingTextBox class. The most likely case is you are setting the DataContext to itself in either the Constructor
this.DataContext = this;
or the XAML
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I had same problem because class of object from which I was pulling out data didn't have get; and set; on its properties.
this didn't work:
public string Name;
but this worked:
public string Name{ get; set; }
I had a similar experience, the ItemsSource binding on a Combobox did not work.
In my case it was a minor mistake, but a difficult one to track until I enabled trace messages.
I simply forget to turn my List into a property :(
// NOPE:
public List<string> Versions;
// YEP:
public List<string> Versions { get; set; }
Maybe this helps someone...
public Window()
{
this.DataContext = this;
InitializeComponent();
}
public string Name {get;}
//xaml
<TextBlock Text="{Binding Name}"/>
Properties Name should be public and { get; }
I had the same problem and in my case I was using bool instead of Boolean. As soon as I changed it, it's working as expected.
Few things to check
1.assign values in properties before InitializeComponent in constructor
public partial class SampleClass: UserControl
{
public SampleClass()
{
ScenarioHeight = System.Windows.SystemParameters.WorkArea.Height - 350;
InitializeComponent();
}
public double ScenarioHeight { get;set;}
2.if its a usercontrol make sure to add userControl as Element in the binding
<ScrollViewer Name="sv" Height="{Binding Path=ScenarioHeight, ElementName=ucSampleClass}" >
This error may also occur when you were previously trying to bind inaccessible or non-existing Enumerable instance using XAML property <ItemsSource>
When you correct the ItemsSource with the correct value XAML doesn't automatically reilitialize the collection of items.
So when I was using the ListBox UI - list representation I faced this in the properties:
Deleting all the items in collection and correcting ItemSource value was the key.
After looking at Shahid's answer, I noticed in my case that I had set the DataContext to a reference in the Loaded event instead of in the constructor. Moving it to the constructor fixed the issue.
I got this error and my case was as simple as setting the String I was binding to from private to public.
Careless mistake writing my backing field.

Failing to successfully expose my ViewModel data to my View for XAML binding

I'm building a Windows Universal app and trying to expose data from my ViewModel to my View so that I can bind it to XAML elements. I have completely commented out all of my code at this point and am just writing lines of test code to try and get it to work, that is what is in the examples below. Binding directly from the View (if I create an object there as a test) does work.
Please help me to understand where I am going wrong, I think I've read every binding tutorial on the internet and still just don't get it.
View (MainPage.xaml.cs):
public MainPage()
{
InitializeComponent();
DataContext = new MainViewModel();
}
ViewModel (MainViewModel.cs):
public class MainViewModel
{
public Term newTerm = new Term
{
TermName = "Table",
TermDescription = "You eat dinner on it"
};
}
XAML (MainPage.xaml):
<StackPanel DataContext="{Binding newTerm}" x:Name="mvvmStack" Orientation="Vertical">
<TextBlock x:Name="mvvmTermName" Text="{Binding TermName, FallbackValue='Fallingback'}" />
<TextBlock x:Name="mvvmDescription" Text="{Binding TermDescription, FallbackValue='Fallingback', TargetNullValue='Unknown'}" />
</StackPanel>
The error I get is:
Error: BindingExpression path error: 'newTerm' property not found on ''. BindingExpression: Path='newTerm' DataItem=''; target element is 'Windows.UI.Xaml.Controls.StackPanel' (Name='mvvmStack'); target property is 'DataContext' (type 'Object')
I have read about this type of error and although I have some idea of what it is trying to say I cannot work out how to fix it. I'm very much a complete beginner with coding, especially C# so please take that into account when answering :-)
Just try to change it from field to a property and it will be working correctly. You can't bind to fields.
EDIT:
private Term _term;
public Term NewTerm{
get{return _term;}
set
{
_term= value;
OnPropertyChanged("Term");
}
}
if you need to add notify the view of changes in the viewmodel you need to implement INotifyPropertyChanged.
check this answer it will provide an example for property changed. https://stackoverflow.com/a/27685925/1448382
If you want to bind the view to sub properties, you have two options depending on the situation:
1- Relative Binding: this scenario is used when you will not modify the properties inside the Term object from the ViewModel i.e. they will be just initialized in the viewmodel and can be modified in the view, just like the way you are doing it. Plesae note, that anything you need to bind to should be a property and not a field.
2- Binding to Viewmodel directly: this scenario is used when you will modify the properties inside the Term object from the Viewmodel after the view load. This way you will need to add properties to the viewmodel for the properties TermName and TermDescription.
public string TermName{
get{return NewTerm.Name;}
set{NewTerm.Name = value;
OnPropertyChanged("TermName");
}//The same is applied for TermDescription
But be aware that you will need to remove the binding on the Stackpanel object since you have defined the properties directly in the Viewmodel.
Try something like that:
<Page.Resources>
<viewModels:MainViewModel x:Key="MainViewModel" />
</Page.Resources>
And then:
<StackPanel x:Name="mvvmStack" Orientation="Vertical">
<TextBlock x:Name="mvvmTermName" Text="{Binding newTerm.TermName, Source={StaticResource MainViewModel} FallbackValue='Fallingback'}" />
<TextBlock x:Name="mvvmDescription" Text="{Binding newTerm.TermDescription, Source={StaticResource MainViewModel} FallbackValue='Fallingback', TargetNullValue='Unknown'}" /></StackPanel>
Of cource newTerm should be an property with INotifyChanged

Bind property of root to value of child element in XAML

I'm not too used to WPF, so this is probably something easy, but I've been struggling with it for a couple hours and can't seem to get how to properly do it.
Say I have a BaseUserControl descending from UserControl with a dependency property Text.
Then in XAML I'm creating a BaseUserControl descendant. I want that property Text to be bound to a control defined in that descendant. Say:
<base:BaseUserControl
... all namespaces ...
xmlns:base="clr-namespace:MyControlsBase"
x:Class="Test.MyTestControl"
Text="{Binding ElementName=MyTextBox, Path=Text}"
<TextBox x:Name="MyTextBox" Text="MyText" />
</base:BaseUserControl>
For some reason, I can't get the MyTextBox to update the Text property on the control itself.
If I add a:
<TextBlock Text="{Binding ElementName=MyTextBox, Path=Text}" />
Anywhere inside the control, the textblock shows the correct TextBox value so the binding definition doesn't seem to be the problem.
I have something else which shows the value of Text in that control... say something like:
<Window>
<StackPanel>
<test:MyTestControl x:Name="MyControl" />
<TextBlock Text="{Binding ElementName=MyControl, Path=Text}" />
</StackPanel>
</Window>
If I update the Text property on MyControlBase from any other means (codebehind, or whatever), it works, and I see the text changed on the textblock... but it doesn't work seem to update when the TextBox inside itself is updated.
Are there any limitations on binding to properties when you are inheriting a control?
PS: the code is obviously artificial and boilerplated for this question
Note: there is obviously something wrong with the binding on that property, since on the trace window, when creating the control, I get a:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=MyTextBox'. BindingExpression:Path=Text; DataItem=null; target element is 'MyTestControl' (Name=''); target property is 'Text' (type 'String')
But it only happens for the `MyTestControl's property, and not for any other binding to the same property inside the XAML.
I believe the problem is that the MyTextBox hasn't been initialized when the BaseUserControl initializes itself and tries to bind with the Text property of the MyTextBox. At this stage, the MyTextBox doesn't exist, as a result you get the 'System.Windows.Data Error: 4 : Cannot find SOURCE for binding with reference'.
You can bind in code-behind after the InitializeComponent() in the CTOR of your MyTestControl.
public MyTestControl()
{
InitializeComponent();
Binding b = new Binding("Text");
b.Source = MyTextBox;
SetBinding(TextProperty, b);
}

WPF Error 40 BindingExpression path error: property not found on 'object'

I'm banging my head on my desk with this binding error.. I have checked several of the postings for the BindingExpression path error and cannot see anything that works with my situation.
Anyway, I have a custom control called IncrementingTextBox. I am trying to disable it whenever the user 'checks' the CheckBox above it.
I have a binding on the CheckBox IsChecked property that is working fine and is firing when it is supposed to. It is correctly setting the UseSensorLength property on the ConfigurationModel.
However, the binding on the IncrementingTextBox IsEnabled property is causing a BindingExpression path error and so doesn't update at all.
As a test, I tried in the code behind to enable and disable the control and it works just fine, but I can't seem to get the Binding to work on it.
Here is a snippet from my xaml:
...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
...
...
<CheckBox Content="Use Sensor Length" Margin="30,6,0,0"
IsChecked="{Binding ConfigurationModel.UseSensorLength, Mode=TwoWay}"/>
<local:IncrementingTextBox x:Name="video_length_textbox" Margin="0,0,0,5"
IsTextEnabled="False"
IsEnabled="{Binding ConfigurationModel.DontUseSensorLength}"
ValueChanged="VideoEventValueChanged"/>
And Here is a snippet from my ConfigurationModel:
public bool DontUseSensorLength
{
get { return !UseSensorLength; }
}
public bool UseSensorLength
{
get { return _useSensorLength; }
set
{
_useSensorLength = value;
OnPropertyChanged("UseSensorLength");
OnPropertyChanged("DontUseSensorLength");
}
}
Here is the error message I am getting in my output window when running the app:
System.Windows.Data Error: 40 : BindingExpression path error:
'ConfigurationModel' property not found on 'object'
''IncrementingTextBox' (Name='video_length_textbox')'.
BindingExpression:Path=ConfigurationModel.DontUseSensorLength;
DataItem='IncrementingTextBox' (Name='video_length_textbox'); target
element is 'IncrementingTextBox' (Name='video_length_textbox'); target
property is 'IsEnabled' (type 'Boolean')
Remember, the 'UseSensorLength' property binding is working fine, but the 'DontUseSensorLength' binding is causing the above 'BindingExpression path error'.
I wrote some other SO answer recently about how to read the binding errors so they make more sense. To summarize, add line breaks to your error message on the colons and semi-colons, and read it from the bottom up.
Your error message is:
System.Windows.Data Error: 40 :
BindingExpression path error: 'ConfigurationModel' property not found on 'object' ''IncrementingTextBox' (Name='video_length_textbox')'.
BindingExpression:Path=ConfigurationModel.DontUseSensorLength;
DataItem='IncrementingTextBox' (Name='video_length_textbox');
target element is 'IncrementingTextBox' (Name='video_length_textbox');
target property is 'IsEnabled' (type 'Boolean')
This can be read from the bottom up as:
The binding failing is the IsEnabled property of an element of type IncrementingTextBox (named video_length_textbox).
The DataItem (DataContext) of the element is an object of type IncrementingTextBox named video_length_textbox
The binding expression it is trying to find is ConfigurationModel.DontUseSensorLength
And the problem the binding is having is that the ConfigurationModel property is not found on the data context object IncrementingTextBox
So your DataContext for "video_length_textbox" is set to itself, and your IncrementingTextBox class does not have a public property called ConfigurationModel
Since I don't see you setting the DataContext for your IncrementingTextBox anywhere in your XAML, check out the code for your IncrementingTextBox class. The most likely case is you are setting the DataContext to itself in either the Constructor
this.DataContext = this;
or the XAML
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I had same problem because class of object from which I was pulling out data didn't have get; and set; on its properties.
this didn't work:
public string Name;
but this worked:
public string Name{ get; set; }
I had a similar experience, the ItemsSource binding on a Combobox did not work.
In my case it was a minor mistake, but a difficult one to track until I enabled trace messages.
I simply forget to turn my List into a property :(
// NOPE:
public List<string> Versions;
// YEP:
public List<string> Versions { get; set; }
Maybe this helps someone...
public Window()
{
this.DataContext = this;
InitializeComponent();
}
public string Name {get;}
//xaml
<TextBlock Text="{Binding Name}"/>
Properties Name should be public and { get; }
I had the same problem and in my case I was using bool instead of Boolean. As soon as I changed it, it's working as expected.
Few things to check
1.assign values in properties before InitializeComponent in constructor
public partial class SampleClass: UserControl
{
public SampleClass()
{
ScenarioHeight = System.Windows.SystemParameters.WorkArea.Height - 350;
InitializeComponent();
}
public double ScenarioHeight { get;set;}
2.if its a usercontrol make sure to add userControl as Element in the binding
<ScrollViewer Name="sv" Height="{Binding Path=ScenarioHeight, ElementName=ucSampleClass}" >
This error may also occur when you were previously trying to bind inaccessible or non-existing Enumerable instance using XAML property <ItemsSource>
When you correct the ItemsSource with the correct value XAML doesn't automatically reilitialize the collection of items.
So when I was using the ListBox UI - list representation I faced this in the properties:
Deleting all the items in collection and correcting ItemSource value was the key.
After looking at Shahid's answer, I noticed in my case that I had set the DataContext to a reference in the Loaded event instead of in the constructor. Moving it to the constructor fixed the issue.
I got this error and my case was as simple as setting the String I was binding to from private to public.
Careless mistake writing my backing field.

Categories

Resources