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.
Related
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
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.
In my program I have a mainWindow that contains a contentPresenter. The mainWindow has a ViewModel called MainWindowViewModel. This viewModel contains properties that are bound to items in the mainWindow.
The Content of my contentPresenter is represented by a UserControl->ViewModel->Model structure.
This is the xaml of the contentPresenter that I am working with.
MainWindow.xaml:
<ContentPresenter Content="{Binding LeftWidget}" IsEnabled="{Binding LeftWidgetEnabled}" ... />
LeftWidget and LeftWidgetEnabled are both properties located in MainWindowViewModel. However, the BindingExpression path error that I receive has to do with LeftWidgetEnabled. For some reason my program is looking for the property in the ViewModel of the contentPresenter's UserControl. This doesn't make much sense to me, because the program deals with the LeftWidget property correctly.
Both properties are located in MainWindowViewModel, so why would the program be looking elsewhere for the LeftWidgetEnabled property? How can I fix this?
Also Note: The way that I set the DataContext of my UserControls are like so...
Under <Window.Resources... in mainWindow.xaml:
<DataTemplate DataType="{x:Type project:LeftWidgetViewModel}">
<local:LeftWidgetUserControl/>
</DataTemplate>
chnage the binding path to (this assumes main window is in fact a window object):
IsEnabled={Binding DataContext.LeftWidgetEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}
does that help? If so then you need to examine the datacontext of your objects as there might be something else going on
also, does the datatype of LeftWidgetEnabled match what is expected by the IsEnabled Property, ie boolean to boolean?
if LeftWidget and LeftWidgetEnabled are in the same datacontext and if LeftWidget is working. then i would say you should check your property in your viemodel wether its really LeftWidgetEnabled.
the xaml looks good for me.
EDIT: LeftWidgetEnabled should be typeof bool not bool?
I have a window which has a usercontrol in it . This usercontrol's RequestObject property bound to SearchArgumentObject property of ViewModel of the window.
This is listing from my window class
<Grid DataContext="{Binding SearchArgumentObject, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<guiLib:RegCardSearchForm x:Name="SearchParametrsUC" Grid.Row="1" RequestObject="{Binding .,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
In Usercontrol class I created dependency property:
This is listing from my userControl class
public static DependencyProperty RequestObjectProperty = DependencyProperty.Register("RequestObject", typeof(RegistrationCardSearch), typeof(RegCardSearchForm));
public RegistrationCardSearch RequestObject
{
get
{
return (RegistrationCardSearch)GetValue(RequestObjectProperty);
}
set
{
SetValue(RequestObjectProperty, value);
}
}
On the level of the usecontrol everything works fine and RequestOject property changed.
But in my window class I can't see modification of SearchArgumentObject property which was made in usercontrol.
How can I get modefied property value? I think answer to this question is very trivial but I can't find solution.
Setting the DataContext on the Grid isn't doing anything but breaking the two-way linking of your properties. Skip the extra step and bind the VM property to the control property that you want to pick up changes from instead:
<Grid>
<guiLib:RegCardSearchForm x:Name="SearchParametrsUC" Grid.Row="1"
RequestObject="{Binding SearchArgumentObject, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
The code for your Window class is setting the DataContext of the Grid to a property obtained from a binding to a property on another object's DataContext further up the tree. Do you have the Window's DataContext set elsewhere?
Let's say that the object which is supplying the SearchArgumentObject is named SearchWindowViewModel. In the code-behind of the Window, you would have the following code (in the constructor, for example):
DataContext = new SearchWindowViewModel();
Now, all the properties that SearchWindowViewModel exposes are available to the Window. To bind the SearchWindowViewModel.SearchArgumentObject to the UserControl's RequestObject property, you would have the following XAML:
<Grid>
<guiLib:RegCardSearchForm x:Name=SearchParametersUC Grid.Row=1
RequestObject={Binding SearchArgumentObject />
</Grid>
If you don't want to set the Window's DataContext, you can set the Grid's DataContext using the same type of code as I used above, and the binding in the XAML would remain the same.
Hope that helps.