BindingExpression path error when creating the Window (no VM bound yet) - c#

I am trying to follow the MVVM pattern, but I'm having a hard time binding a VM property to a dependency property of a UserControl of mine. I'm not sure whether I got the initialization flow completely wrong or I just missed a detail.
Here is my Application Startup event handler:
private void Application_Startup(object sender, StartupEventArgs e)
{
MainViewModel viewModel = new MainViewModel();
MainWindow window = new MainWindow();
viewModel.BindView(window);
window.Show();
}
This is my MainViewModel (ViewModelBase comes from MVVM Light):
public class MainViewModel : ViewModelBase
{
private Project m_currentProject = new Project();
public Project CurrentProject
{
get { return m_currentProject; }
private set
{
m_currentProject = value;
}
}
}
And this is my MainWindow.xaml:
<Window x:Class="WST_Desktop.MainWindow"
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:WST_Desktop"
xmlns:controls="clr-namespace:WST_Desktop.Controls"
mc:Ignorable="d"
Title="MainWindow" Height="540" Width="960">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<controls:ProjectControl Project="{Binding Path=CurrentProject, Converter={wstd:DebugConverter}}" />
</Grid>
</Window>
I get this error from the DataBinding Listener (lines broken for readability):
BindingExpression path error: 'CurrentProject' property not found on 'object' ''Project' (HashCode=30400195)'.
BindingExpression:Path=CurrentProject; DataItem='Project' (HashCode=30400195);
target element is 'ProjectControl' (Name=''); target property is 'Project' (type 'Project')
The error is reported on the line with new MainWindow() from the startup handler. Also, the DebugConverter I attached to the binding is never hit.
I think I understand that the MainWindow is created without a DataContext (it will be bound in the next line), and so it fails binding the property of the ViewModel (that it doesn't know of yet, of course). Am I correct? How do I fix this?
PS
Of course, the ProjectControl has a public property named Project of the correct Project type.
And this is the BindView method that gets called in the Startup event handler:
public static void BindView(this ViewModelBase viewModel, Control i_control)
{
i_control.DataContext = viewModel;
}
Which basically translates to
window.DataContext = viewModel;
MORE INFO
I tried to change this
<controls:ProjectControl Project="{Binding Path=CurrentProject, Converter={wstd:DebugConverter}}" />
to this
<controls:ProjectControl DataContext="{Binding Path=CurrentProject, Converter={wstd:DebugConverter}}" />
and the error is gone. I'm not sure which one is correct, though: should the Project object (which is sort of the "root" model object of my ProjectControl) be a dependency property or the DataContext?

Let's see what this binding error is all about:
BindingExpression path error: 'CurrentProject' property not found on
'object' ''Project' (HashCode=30400195)'.
BindingExpression:Path=CurrentProject; DataItem='Project'
(HashCode=30400195);
target element is 'ProjectControl' (Name=''); target property is 'Project' (type 'Project')
You can get a lot of info out of it.
First of all, the Binding Engine is telling you that there is an error in the Path property of your Binding, because the Binding Engine cannot find a property named CurrentProject on an object that is serving as current DataContext. You should know, that the Binding Engine uses the DataContext object as the source when you don't explicitly specify a different source (e.g. using ElementName or Source properties of the Binding).
The Binding Engine states also, that the current DataContext object is of type Project and not of type MainViewModel. It is the fact that should make you look suspicious at the code.
The second line just repeats the info: the DataContext (or DataItem in this message) is an object of type Project, and we're trying to get its property CurrentProject (what is obviously not possible, because this property doesn't exist).
The third line shows you your Binding's target: it is an object of type ProjectControl and its property named Project (of type Project).
So summarizing: the view model the Binding Engine is dealing with is not the one you're thinking you're binding to. There is only one explanation for this: somewhere, there's some code or another Binding that sets the view's DataContext to an object of type Project.
And as you mention in the comment, you have found that in your code.

What is this BindView method?
Just try
window.DataContext = viewModel;
Instead of
viewModel.BindView(window);
Edit: Okay as the error message states you are looking for a CurrentProject property on an object of type Project. I would suggest you bind the DataContext of your Grid to your viewmodel aswell. This should solve your problem.
To verify that solves your problem you can just adjust the binding
<Grid DataContext="{Binding ElementName=foo, Path=DataContext}">
and set the name of your window Name="foo".

Related

Binding with relative source

I am trying to understand how the RelativeSource works.
With the setup below I'd expect to see the text "I am the MainViewModel" displayed on the form, however I see an error in the debugger and no text on the MainWindow:
Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='UnderstandingBindings.ViewModels.MainViewModel', AncestorLevel='1''. BindingExpression:Path=SomeProperty; DataItem=null; target element is 'TextBlock' (Name='myText'); target property is 'Text' (type 'String')
I have a ViewModel like this:
class MainViewModel
{
public string SomeProperty { get => "I am the MainViewModel"; }
private readonly ChildViewModel _child = new ChildViewModel();
public ChildViewModel Child => _child;
}
class ChildViewModel
{
public string SomeProperty { get => "I am the ChildViewModel"; }
}
the MainWindow XAML looks like:
<Window x:Class="UnderstandingBindings.Views.MainWindow"
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:UnderstandingBindings.Views"
xmlns:vm="clr-namespace:UnderstandingBindings.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel x:Name="pnlMain">
<TextBlock x:Name="myText" Text="{Binding SomeProperty, RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}}}"/>
</StackPanel>
</Window>
The data context is assigned like this:
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = _viewModel.Child;
}
}
wpf looks for the Ancestors of the xaml element the binding is declared on. You can think of it as walking up the Visual Tree.
You'd use it to Bind to a property thats on that Ancestor or have to bind to a viewmodel by going through it's DataContext Property. So for example:
<TextBlock x:Name="myText" Text="{Binding DataContext.Child.SomeProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
That only works if the DataContext is set to the MainViewModel in your example tho. If you want the Binding to go the direction you expected then you need to add a reference to the Parent Viewmodel on the Child.
The declaration of a relative source within a binding does the following.
Gets or sets the binding source by specifying its location relative to the position of the binding target.
This means it sets the Source property of the binding to an element in the visual tree. This can be the current element (Self) or an ancestor (e.g. StackPanel is an ancestor of the TextBlock it contains) or a templated parent in case of control templates. This depends on the Mode that you set. A relative source allows you to specify a property path on that element in a binding, like its DataContext or Tag or any other property.
The error that you get translates to: I searched for an instance of type MainViewModel in the visual tree starting from TextBlock. Then I checked the next ancestor StackPanel, which is not a MainViewModel. Then I checked the next ancestor Window, which is also not a MainViewModel. There is no other ancestor, so I could not find anything.
You are misusing relative source here. View models are not part of the visual tree, but act as data context for elements. For your example using the data context the right way is enough. Once you set the data context on MainWindow, it is inherited in all child controls, if not specified otherwise, e.g. explicitly assigning a different data context on an element. Consequently, the TextBlock, which is a child of MainWindow will get the same data context that you assigned to the DataContext property of MainWindow.
The data context in your example is an instance of ChildViewModel, so in order to bind to its SomeProperty, you do not need a relative source binding, just a property path which is automatically resolved using the DataContext (which is set as the binding source) of the corresponding control.
<TextBlock x:Name="myText" Text="{Binding SomeProperty}"/>
This will lead to the following text: I am the ChildViewModel
If you want to bind to the SomeProperty of MainViewModel, you should set the DataContext accordingly.
public MainWindow()
{
InitializeComponent();
this.DataContext = _viewModel;
}
The binding in TextBlock is the same as above, if you want to display the SomeProperty of MainViewModel.
<TextBlock x:Name="myText" Text="{Binding SomeProperty}"/>
If you want to bind the SomeProperty of the ChildViewModel instead, you can change the path.
<TextBlock x:Name="myText" Text="{Binding Child.SomeProperty}"/>
In both example, it will lead to the following text: I am the MainViewModel

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

MVVMLight within another MVVMLight project

I am working on a MVVMLight / WPF project and need to add a chunk of functionality which will include multiple views and viewmodels. I know this same bit of functionality will be used in other projects in the near future so I would like to make this functionality its own project that I can add to other solutions as needed wiuth little or no modification.
I started by adding a second MVVMLight project (Beta), removing the standard MainWindow.xaml and MainViewModel.cs files and created a simple UserControl and associated View Model.
<UserControl x:Class="Beta.View.TestView"
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:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
DataContext="{Binding Test_VM, Source={StaticResource Locator} }">
<Grid>
<TextBlock Text="{Binding WelcomeMessage}" />
</Grid>
</UserControl>
public class TestViewModel : ViewModelBase
{
#region Properties
public string WelcomeMessage
{
get
{
return "Hello World!";
}
}
#endregion Properties
#region Constructors
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
}
#endregion Constructors
}
I am able to add Beta as a reference to the original project (Alpha) and display the view by inserting the view into a stack panel like so:
<StackPanel Name="MasterStackPanel"
DockPanel.Dock="Top">
<beta:TestView />
</StackPanel>
Everything appears to work properly when doing this. The issue I am having is when I try to bind a Property from TestViewModel to TestView.
In TestView, if I do this:
<TextBlock Text="Hello World" />
the TestView displays correctly at runtime. But when I bind the TextBlock to a property like so:
<TextBlock Text="{Binding WelcomeMessage}" />
The message does not display and the locator for Beta appears to be ignored (the datacontext is not being bound) and I am getting the following error from Snoop:
System.Windows.Data Error: 40 : BindingExpression path error: 'WelcomeMessage' property not found on 'object' ''MainViewModel' (HashCode=51013215)'. BindingExpression:Path=WelcomeMessage; DataItem='MainViewModel' (HashCode=51013215); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'Test_VM' property not found on 'object' ''ViewModelLocator' (HashCode=22749765)'. BindingExpression:Path=Test_VM; DataItem='ViewModelLocator' (HashCode=22749765); target element is 'TestView' (Name=''); target property is 'DataContext' (type 'Object')
I believe this means that the binding of Test_VM & WelcomeMessage are trying to be found via the Alpha Locator and not the Beta Locator. I am using the ViewModelLocator that is created by default when starting a MVVMLight project in each project.
Is it possible to have an second 'Locator' and if so what do I need to do to make it work?
I think you should only have one Locator in the application root of the system and use the "MvvmLightLibs" library in the library project and reference it in the alpha project and add a TestViewModel-Property in the locator.

Get DataContext From XAML

I deleted the CodeBehind of my MainWindow.xaml, cause I'm doing a small project where I literally must do that.
So I'm creating an instance of my ViewModel in this way over xaml:
<Grid.DataContext>
<lib:StartPageViewModel />
</Grid.DataContext>
Well now, I need this DataContext in my Code (StartPageViewModel), as I want to open an other solution (For more Informations take a look here).
Any Ideas, on how I can get this DataContext?
At least I solved the problem.
Actually I didn't really need to use the DataContext for this:
public static DTE2 GetDTE(DataContext dataContext)
{
ICustomTypeDescriptor typeDescriptor = dataContext as ICustomTypeDescriptor;
Debug.Assert(typeDescriptor != null, "Could not get ICustomTypeDescriptor from dataContext. Was the Start Page tool window DataContext overwritten?");
PropertyDescriptorCollection propertyCollection = typeDescriptor.GetProperties();
return propertyCollection.Find("DTE", false).GetValue(dataContext) as DTE2;
}
I changed the code to the following, it works now perfectly, I can open Solutions without using the DataContext:
public static DTE2 GetDTE()
{
return (DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
}
A ViewModel shouldn't know anything about the View.
So when applying 'proper' MVVM, you shouldn't be trying to get to the DataContext from within the ViewModel.
The code of the StartPageViewModel IS part of the object that is put in the DataContext. This means that you can access the object by using this in the code of the ViewModel.
If you are looking for the Grid (or even higher up the VisualTree) you could pass it using a property in the Xaml:
<Grid Name="MyGrid">
<Grid.DataContext>
<lib:StartPageViewModel MyParent={Binding ElementName=MyGrid} />
</Grid.DataContext>
</Grid>
Still, if you do that, you are adding knowledge about the View to the ViewModel.
I've never declared a viewmodel in a XAML file, but have you tried:
viewName.DataContext as ViewModelType;
BTW, deleting the code-behind is usually a good practice.

Categories

Resources