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.
Related
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".
I'm using Template 10 with a hamburger menu for my project.
To change the buttons dynamically depending on wether the user is logged in or not, I figured I'd create a ViewModel for the Shell. Unfortunately, it would seem the DataContext is not correctly targeted to the ViewModel.
I've added the data context to the Shell.xaml, but bindings aren't doing their thing. Is there a better way of going about this, or am I doing something wrong.
Shell.xaml snippets:
Setting the data context:
<Page.DataContext>
<viewModels:ShellViewModel x:Name="ViewModel" />
</Page.DataContext>
Binding visibility to viewmodel:
<Controls:HamburgerButtonInfo AutomationProperties.Name="My Journey"
ClearHistory="False"
PageType="views:MyJourneyPage"
Visibility="{Binding LoggedIn, Converter={StaticResource BooleanToVisibilityConverter}}">
ShellViewModel snippet:
private bool _loggedIn;
public bool LoggedIn { get { return _loggedIn; } set { Set(ref _loggedIn, value); } }
Visual Studio is reporting:
Error: BindingExpression path error: 'LoggedIn' property not found on 'Template10.Controls.HamburgerButtonInfo'. BindingExpression: Path='LoggedIn' DataItem='Template10.Controls.HamburgerButtonInfo'; target element is 'Template10.Controls.HamburgerButtonInfo' (Name='null'); target property is 'Visibility' (type 'Visibility')
Resolved by changing the visibility binding to:
"{x:Bind Path=ViewModel.LoggedIn, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverterInverse}}"
It doesn't see the path for the viewmodel and that property you created for the login, Path = LoggedIn, DataContext= ViewModel depending on the depth you might have to find the combination for the DataContext. This is more than likely due to the DataContext that the DataItem sees right now, related to the collection it is part of in the hamburger control.
I am currently running into some trouble with binding objects to a WPF ListBox. The number of Elements is populated correctly but the Name property cannot be accessed. I get the following error message:
System.Windows.Data Error: 40 : BindingExpression path error: 'Name' property not found on 'object' ''__ComObject' (HashCode=17252513)'.
BindingExpression:Path=Name; DataItem='__ComObject'
(HashCode=17252513); target element is 'TextBlock' (Name=''); target
property is 'Text' (type 'String')
However, I am able to print this property correctly to the console at runtime.
It looks like the object is not casted correctly
XAML:
<ListBox Name="lbSelectConfiguration" Margin="10" ItemsSource="{Binding Configurations}">
<ListBox.ItemTemplate>
<StackPanel Margin="5" Orientation="Horizontal">
<Image Source="/Resources/Configuration.png" Margin="0,0,8,0" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind
this.DataContext = viewModel;
this.viewModel = viewModel;
foreach (Configuration config in this.viewModel.Configurations)
{
Console.WriteLine(config.Name);
}
Can you help me out? Thank you.
Because __ComObject is mentioned in the error, I am presuming that the collection of objects you are binding to, are actually COM objects that implement the "Configuration" interface, rather than CLR ones.
(whether that COM object is actually implemented using C# (with ComVisible(true)) or C++ is immaterial, it's the fact you are using an "interface" - am presuming it is coming from an "COM interop library" i.e. after you did an add reference to a COM library?).
You can try this:
<TextBlock Text="{Binding (mynamespacewithinterface:Configuration.Name)}" />
Otherwise you can look at these links to understand the difficulty in the Binding system resolving bindings (COM interfaces and NET interfaces each have their own quirks/limitations), and some workarounds:
https://social.msdn.microsoft.com/Forums/en-US/69389bb5-ba90-4a22-8a75-e1a21f1d3a16/comobject-in-datatemplate?forum=wpf
http://badecho.com/2012/07/adding-interface-support-to-datatemplates/
https://social.msdn.microsoft.com/Forums/vstudio/en-US/1e774a24-0deb-4acd-a719-32abd847041d/data-templates-and-interfaces
WPF databinding to interface and not actual object - casting possible?
https://social.msdn.microsoft.com/Forums/vstudio/en-US/1e774a24-0deb-4acd-a719-32abd847041d/data-templates-and-interfaces?forum=wpf
http://blog.pmunin.com/2012/01/xaml-datatemplates-binding-to-interface.html
http://leecampbell.blogspot.co.uk/2008/09/generic-binding-in-wpf-through-explicit.html
http://www.dev102.com/2008/04/23/wpf-datatemplate-for-interfaces-not-supported/
As far as I can remember, ComObjects are dynamic in .NET, so the compiler will never recognise the names of the object's properties at compile time. If that's really important for you, then just create a .NET wrapper object that implements the INotifyPropertyChanged interface and copy all of the object's properties to your WPF object. The compiler will then obviously be able to read the names of the properties.
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
My UserControl requires binding to the ancestor (the ancestor being the MainWindow) and to itself (it's code behind).
To bind to the ancestor, I'm using
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1,AncestorType=Window}}">
To bind a control to the code behind (and thus using the 'local' DataContext), I'm using
<TextBlock Text ="{Binding MyUC3Property}" Name="MyName" />
and in the code behind, setting it like
this.MyName.DataContext = this;
The above works fine, where I can bind to the codebehind and to the ancestor.
Now, I still want to bind to the code behind and the ancestor but set the DataContext in the XAML only (if possible).
I've tried
<TextBlock Text ="{Binding MyUC3Property}" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}" />
and ensured the constructor does not set the DataContext (since I want it all done in the XAML) - (although even if I do set this.DataContext = this; the error persists)
and the output window tells me there is a binding error.
System.Windows.Data Error: 40 : BindingExpression path error: 'MyUC3Property' property not found on 'object' ''TextBlock' (Name='')'. BindingExpression:Path=MyUC3Property; DataItem='TextBlock' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
I guess I'm missing something obvious, but I can't tell what.
You should be able to bind to the user control the same way as you do to the window:
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1,AncestorType=UserControl}}">
What you have tried was referring to the relative source Self from the TextBox. However, in that context, Self refers to the TextBox, not to the enclosing user control.
for usercontrols you should never set the datacontext to self. check to comment from H.B. from here
i use ElementName Binding
<UserControl x:Name="uc">
<TextBlock Text="{Binding ElementName=uc, Path=MyDependencyPropertyDefinedInMyUserControl}"/>
using the usercontrol:
<Window>
<MyUserControl MyDependencyPropertyDefinedInMyUserControl="{Binding Path=MyValueForTheTextBox}"/>
i try to explain it a little bit for your textbox case (ignor my poor english btw :))
if you want create a usercontrol with a textbox and this usercontrol/textbox should show the text from differrent viewmodels in different views - then you have a problem as far as the viewmodels have different propertynames. now the dependency property in your usercontrol come into the game. you create a DP where all your viewmodels can bind to and you bind your textbox within your usercontrol just to the DP from your usercontrol.
First thing is that you should probably push your parent DataContext to the lower levels. This will give you "God" ViewMode shared between all nested screens.
Second is that you should probably use something like MVVMLights Messanger to have cleaner separation.