Databinding a TabItem to a parent Window - c#

I'm trying to clean up the way I use DataContexts with my UserControls, and currently am running into a problem where I need to databind a UserControl inside of a TabItem to the parent Window's DataContext.
Here is a sketch of what my Window looks like:
As you can see, this Window owns a TabControl that contains TabItems that are dynamically added via the "Tabs" ItemSource. Databinding at this point is working because "Tabs" gets populated with Tab 1.
Tab 1 contains a UserControl that needs access to multiple string properties in the DiagnosticsViewModel, but when I run my application, the Output window indicates that all bindings have failed. For example:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='System.Windows.Window', AncestorLevel='1''.
BindingExpression:Path=Property1; DataItem=null; target element is
'Tab1UserControl' (Name=''); target property is 'UCName' (type
'String')
The XAML for the UserControl in Tab 1 looks something like this:
<Grid>
<uc:Tab1UserControl UCName="{Binding Property1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Grid>
where UCName is a String DependencyProperty.
If I am telling WPF that I want to look up the tree and bind to the DataContext for the nearest Window, and my Window's DataContext is set to DiagnosticsViewModel, why isn't it using it for my UserControl's DataContext? I have not set DataContext = this in my UserControl, as I have done improperly many times in the past, with the expectation that my UserControl will be able to inherit the DataContext from its parent.
I would like to see if Snoop can shed light on my problem, but this GUI is being displayed from a MFC application, and Snoop doesn't seem to be able to attach to my WPF dialog.

If you change the source of a binding using RelativeSource, ElementName or the like, the binding will be directly to the element you specify - not its data context. Which means that in your code the user control will try to bind to a property called Property1 on the Diagnostics class itself.
Try using
<Grid>
<uc:Tab1UserControl UCName="{Binding Path=DataContext.Property1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Grid>
in the code for your user control and see if that fixes the problem.
(As an aside, the idea of the user control itself knowing that the window it belongs to will have a datacontext with a given property seems like a code smell to me, especially since the point of user controls is that they're reusable - it'd feel better to have a dependency property on the user control and then bind that to the appropriate property when you use it. This may just be due to me lacking context, though.)

Related

What is different between {binding} and inherit in wpf datacontext?

I have some problems about datacontext binding.
My app has virtualization listbox.
Sometimes Button is not fired dataContextChanged.
So I found this.
<Grid DataContext={Binding ~~>
<Button DataContext={Binding}/>
</Grid>
<Grid DataContext={Binding ~~>
<Button/>
</Grid>
My Code is first one. but It's not fired DataContextChanged sometimes, So I changed code to second one.
snoop said first one is from inherit, and second one is from parentTemplate.
What is different first one and second one?
TL;DR:
SomeProperty={Binding} will bind the SomeProperty to the ENTIRE DataContext object from the parent. SomeProperty does not have to be the DataContext from the child. In this special case I don't think there is any difference, since the DataContext is inherited anyways. You are simply explicitly stating, what is already the default. Discussed here
More Info:
Here is an explanation from the official documentation for
<Button DataContext={Binding}/>
As long as the binding already has a data context (for example, the
inherited data context coming from a parent element), and whatever
item or collection being returned by that context is appropriate for
binding without requiring further path modification, a binding
declaration can have no clauses at all: {Binding}. This is often the
way a binding is specified for data styling, where the binding acts
upon a collection. For more information, see Using Entire Objects as a Binding Source.
and from here
<ListBox ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="true"/>
The above example uses the empty binding syntax: {Binding}. In this
case, the ListBox inherits the DataContext from a parent DockPanel
element (not shown in this example). When the path is not specified,
the default is to bind to the entire object. In other words, in this
example, the path has been left out because we are binding the
ItemsSource property to the entire object. (See the Binding to
collections section for an in-depth discussion.)
In general, the DataContext is inherited from parent elements. So in your 2nd example the button gets the DataContext from the Grid. Example
"ItemsControl" are special: Example
I don't know what ~~ is, is this meant to be a placeholder?
Some more information on Bindings and Markup Extensions:
Link1
Link2
Link3

Dependency Property Datacontext

I have a usercontrol, and there is a Datacontext set for it. This usercontrol contains also a Dependency-Property. Now, i want simply bind to this property.
I think the problem has something to do with the wrong datacontext.
The dependency-Property in my usercontrol (called TimePicker) looks like this:
public TimeSpan Time
{
get { return (TimeSpan)GetValue(TimeProperty); }
set
{
SetValue(TimeProperty, value);
OnPropertyChanged();
}
}
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof (TimeSpan), typeof (TimePicker));
I try to use it like this:
<upDownControlDevelopement:TimePicker Grid.Row="1" Time="{Binding Path=TimeValue}" />
When i do this i get the following binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'TimeValue' property not found on 'object' ''TimePicker' (Name='TimePickerControl')'. BindingExpression:Path=TimeValue; DataItem='TimePicker' (Name='TimePickerControl'); target element is 'TimePicker' (Name='TimePickerControl'); target property is 'Time' (type 'TimeSpan')
Any help would be highly appreciated
Greetings Michael
PS: you can download the code at here
Although this has now been solved there seems to be some, in my opinion, inappropriate use of the DataContext.
When developing a custom reusable control, you should not set DataContext at all. What the DataContext will be, that is for the user of the control to decide, not for the developer. Consider the following common pattern of code:
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
</Grid>
Notice that here, you are using the Grid control. The developer of the control (in this case, the WPF team), didn't touch the DataContext at all - that is up to you. What does it mean for you as a control developer? Your DependencyProperty definition is fine, but you shouldn't touch the DataContext. How will you then bind something inside your control to the DependencyProperty value? A good way is using a template (namespaces omitted):
<MyTimePicker>
<MyTimePicker.Template>
<ControlTemplate TargetType="MyTimePicker">
<!-- Stuff in your control -->
<TextBlock Text="{TemplateBinding Time}" />
<TextBox Text="{Binding Time, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
<MyTimePicker.Template>
</MyTimePicker>
Note that TemplateBinding is always one-way only, so if you need any editing at all, you need to use normal binding (as you can see on the TextBox in the example).
This only means that the TextBlock/Box inside your control will get its Time value from your custom control itself, ignoring any DataContext you might have set.
Then, when you use the control, you do it like this (added to my first example):
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
<MyTimePicker Time="{Binding TimeValue}" />
</Grid>
What just happened here is that the MyTimePicker does not have DataContext set anywhere at all - it gets it from the parent control (the Grid). So the value goes like this: Data-->(binding)-->MyTimePicker.Time-->(template binding)-->TextBlock.Text.
And above all, avoid doing this in the constructor of your custom control:
public MyTimePicker()
{
InitializeComponent();
DataContext = this;
}
This will override any DataContext set in XAML, which will make binding a huge pain (because you'll have to always set Source manually). The previous example would not work, and this wouldn't work either:
<MyTimePicker DataContext="{Binding Data}" Time="{Binding TimeValue}" />
You would think this is OK, but the DataContext will be resolved in the InitializeComponent() call, so the value will be immediately overwritten. So the binding to TimeValue will look for it in the control instead (which will, of course, fail).
Just don't touch the DataContext when developing a control and you'll be fine.
You don't need to override the data context of user control. You can use RelativeSource to point your binding source property i.e. TimeValue to any other source you like. E.g. If you have the source property in your window's class. You could simply point your binding target to the source in window's data context as follows:
{Binding Path=DataContext.TimeValue, RelativeSource={ RelativeSource AncestorType={x:Type Window}}}
Your error states that 'TimeValue' property not found on 'object' 'TimePicker', which means that the WPF Framework is looking at the 'TimePicker' object to resolve the 'TimeValue' property value. You must have somehow set the DataContext of the Window or UserControl that contains the 'TimePicker' object to an instance of the 'TimePicker' object.
Instead, it should be set to an instance of the class that declares the 'TimeValue' property. If you're using a view model, then you should set it to an instance of that:
DataContext = new YourViewModel();
If the 'TimeValue' property is declared in the Window or UserControl then you can set the DataContext to itself (although generally not recommended):
DataContext = this;
Please note that when data binding to the 'Time' property from inside your TimePicker control, you should use a RelativeSource Binding:
<TextBlock Text="{Binding Time, RelativeSource={RelativeSource
AncestorType={x:Type YourLocalPrefix:TimePicker}}}" ... />
Normally we are not setting datacontext directly.If u want to set datacontext create an instance of your usercontrol and set datacontext individually to each one.

BindingExpression path errors when switching ViewModels in MVVM application

First things first, some context. If you're familiar with the problem, skip down to the BindingExpression part. This is my first major project in WPF, so I am still quite new to the MVVM pattern. Here is the only other similar question I have found, whose lacklustre answer doesn't really enthuse me much.
I have/am building a .NET 3.5 WPF application and I am using MVVM (implemented myself, no framework). Within this, I have a number of Views and ViewModels. These reside within a master ApplicationView and ApplicationViewModel respectively.
The way I change views is through using XAML DataTemplate elements in the ApplicationView, like so:
<DataTemplate DataType="{x:Type viewmodels:InitViewModel}">
<views:InitView />
</DataTemplate>
And then in the main body I have a ContentControl which binds to a property in ApplicationViewModel
<ContentControl Content="{Binding CurrentPageViewModel}"/>
When I run the application, all of this appears to work fine, and does exactly what is intended. However, when I look at the Debug output after the run, I get a lot of BindingExpression errors.
Here is one for example. I have a property, SplashText, in my InitViewModel. This is bound to a textblock in the splash screen (InitView). When the splash screen ends and I switch out the viewmodel, I get the following:
System.Windows.Data Error: 39 : BindingExpression path error: 'SplashText' property not found on 'object' ''MainMenuViewModel' (HashCode=680171)'. BindingExpression:Path=SplashText; DataItem='MainMenuViewModel' (HashCode=680171); target element is 'TextBox' (Name='FeedBackBox'); target property is 'Text' (type 'String')
I understand that this is because the bindings still exist, but the CurrentPageViewModel property of the DataContext has changed. So what I want to know is:
Is this a fleeting problem, i.e. are the views disposed of when not being used or do they (and the bad bindings) sit there in memory indefinitely?
Is there a way I can clean up or deactivate these bindings while the view is inactive?
What sort of performance knock is it going to have on my application if I leave these alone?
Is there a better way of switching views which avoids this problem?
Thanks in advance, and apologies for the monolithic question.
Edit 03/09/13 - Thanks to Jehof, Francesco De Lisi and Faster Solutions for pointing out that it is pointless to set sub-views datacontext as {Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}} because the ContentControl takes care of the datacontext.
Your specific example is not reproducible in .NET 4.5, which probably means Microsoft has fixed the problem meantime.
Nevertheless, a similar problem exists when Content and ContentTemplate are both data-bound. I am going to address that problem, which is also likely to solve problems in .NET 3.5 if anyone is still using it. For example:
<ContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}" />
Or when ContentTemplate is determined by DataTrigger:
<ContentControl Content="{Binding Content}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Choice}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
</DataTrigger>
<DataTrigger Binding="{Binding Choice}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
In both cases, one gets binding errors similar to those OP observed.
The trick here is to ensure that changes to Content and ContentTemplate are performed in just the right order to prevent binding errors. I've written DelayedContentControl, which ensures that Content and ContentTemplate are changed at the same time and in the right order.
<jc:DelayedContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}">
Similarly for the DataTrigger case.
You can get DelayedContentControl from my opensource JungleControls library.
It looks like your DataContext goes to MainMenuViewModel while your property belongs to another ViewModel generating the error.
The CurrentPageViewModel value before and after the splash screen changes losing its Binding while switching view.
The problem is dued to DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
In fact, CurrentPageViewModel = InitViewModel when your application starts, but the problem is that every View has the same DataContext (i.e. InitViewModel at first) but I'm sure the ViewModels haven't the entire pool of needed properties to satisfy view bindings.
An example to understand:
ViewX has a binding to PropertyX, managed in ViewModelX.
ViewY has a binding to PropertyY, managed in ViewModelY.
Both have DataContext = CurrentViewModel.
On the startup CurrentViewModel = ViewModelX and both ViewX and ViewY have DataContext = ViewModelX. But this is wrong! And probably will generate an error.
What I usually do is to set in the View class the DataContext (cs or XAML if you prefer) with the corresponding View Model to be sure it fits. Then, when needed, I call a refresh method to update my values every time I switch page. If you have shared properties consider to use a Model to centralize your informations (and values).
A sample image from http://wildermuth.com/images/mvvm_layout.png
Obviously the Views are the Controls wrapped by your MainWindow.
Hope it's clear.
Lets answer your questions in sequence:
You probably already know the answer to this. When .Net garbage collects it'll remove your View object from the heap. But until this time your View object is still bound to the main DataContext on your page and will react to DataContext changed events.
The obvious thing to do is to set the Views DataContext to null. DataContext is a dependency property so the null values scope will just be your View.
As the other/lackluster answer said, it'll slow you down a bit but not a lot. I wouldn't worry too much about this.
Yes. Here's a useful thread on view navigation options: View Navigation Options
I'd also suggest looking at a framework. Something light-weight like MVVM Light will solve a bunch of problems for you with very little integration. It's ViewModelLocator pattern also does what you're doing, but without the side-effects and provides a whole bunch of cleanup options.
You can omit the binding of the DataContext in your Views
DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
cause the DataContext of your View is the DataContext of the ContentControl and that gets set by your binding of the Content-Property.
So, when your property CurrentPageViewModel is set to an InitViewModel the ContentControl will use the InitViewModel as DataContext and use the InitView as ContentTemplate and it will set his own DataContext as DataContext of the InitView.

When to use FindAncestor?

In the image below, why is FindAncestor needed and why is AncestorType not UserControl?
If the UserControl was inside a StackPanel, would AncestorType be StackPanel?
In other words, what does the parent control have to do with binding to the viewmodel.Message? Why is all the other xaml needed in the Binding?
It wouldn't need to specify AncestorType=UserControl, since that's the default context. If you omit the "RelativeSource" parameter, it will bind to the local DataContext.
In this case, the UserControl and the Window it is contained in must have different DataContexts. Since a UserControl generally doesn't know the name of its parent at design time, you can at the least usually assume it's only going to be in a single Window and create a binding to the Window's DataContext using RelativeSource as shown.
This can be common because in an application, the Window might have a DataContext for information about the application as a whole, but the current UserControl might be focused on a particular piece of data (such as an Employee). In this case all the default bindings point to the Employee, but the UserControl might want to bind to something in the parent DataContext too.
Why is FindAncestor needed?
Here FindAncestor is used to access to the Window object to get an access to the DataContext of Window object.
Why is AncestorType not UserControl?
Because the DataContext of the UserControl object may be not the same as the DataContext of Window object.
Normally it's the same but you can redefine it.
If the UserControl was inside a StackPanel, would AncestorType be StackPanel?
No if the StackPanel is itself in your Window.
Don't forget that you target the Type of the graphical object that contain the DataContext you want.
In this case, the developer just knew that the object that had the datacontext they were looking for was a Window. If, for example, a data context was set on a stackpanel and you wanted something from that datacontext, then you would use the AncestorType = Window. Since there are two datacontexts, and the user wanted to bind something to the outside one, FindAncestor was used.

usercontrol button binding not responding to relaycommand on mainviewmodel

I have a MainWindow bound to its mainViewModel. inside the MainWindow I have a usercontrol defind like this
<vm:StatPanel DockPanel.Dock="Right" DataContext="{Binding Source={StaticResource viewModel}}" Loaded="StatPanel_Loaded" />
inside that usercontrol I have a datagrid with buttons. The goal is when the buttons are clicked to change a datagrid on the MainWindow xaml. this is what my usercontrol datagrid looks like
<Button Content="{Binding Path=sector}" Command="{Binding Path=filterGridCommand}"></Button>
when I run the application I get the following error.
System.Windows.Data Error: 40 : BindingExpression path error: 'filterGridCommand' property not found on 'object' ''mdSectorDetail' (HashCode=42410114)'. BindingExpression:Path=filterGridCommand; DataItem='mdSectorDetail' (HashCode=42410114); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
I am using a command relay that is located in the MainViewModel. My problem is I dont know how to reference that mainViewModel, i have tried several of the suggested solutions like the following
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type }}}"
Please any suggestions will be helpful. Thank you.
You can use snoop to find out what is your DataContext of Button. I think that in your case is wrong DataContext. If you give me all code of UserControl, I will write you a proper data bidning.
i use a empty "marker" interface for such things.
public interface IMyCommandDataContextHelper {}
the control/window which has the datacontext i wanna reach with relative source has to implement the empty interface.
public partial class MainWindow : IMyCommandDataContextHelper
then i can easily write my xaml with relative source
{Binding Path=DataContext.filterGridCommand, RelativeSource={RelativeSource AncestorType={x:Type local:IMyCommandDataContextHelper}}}
ps: Properties should be PascalCase :)
public ICommand FilterGridCommand {get{...}}

Categories