Binding to DependancyProperty - wpf

My XAML won't bind and I can't see why
Using MVVM, I have the following code in my MyView.xaml
<Expander Header="{Binding ThresholdName}" IsExpanded="False" >
<properties:PropertiesVm Name="{Binding ThresholdName}" />
</Expander>
And this has a DataTemplate of MyViewModel.cs
As you can see, I'm using the ThresholdName twice. When I run my application, I can see the ThrehsoldName in the Header of the Expander indicating that the binding at this point is fine. The issue is binding to my "properties" object.
The Properties View is
<Grid>
<TextBlock Text="{Binding Name}" />
</Grid>
And the ViewModel(which inherits from DependancyObject) is
public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(PropertiesVm));
public string Name
{
get { return (Convert.ToString(GetValue(NameProperty))); }
set { SetValue(NameProperty, value); }
}
The error message I get is
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Time; DataItem=null; target element is 'PropertiesVm' (HashCode=6494098); target property is 'Name' (type 'String')
And within my Dictionary, I have set the DataTemplate as
<DataTemplate DataType="{x:Type viewModel:PropertiesVm}">
<view:PropertiesView />
</DataTemplate>
This post suggests adding x:Name but that didn't help
Why does the property Name in my Properties ViewModel never get set? Or can I only do this with a UserControl, binding to code behind?

You've got things a bit about face.
Your Expander content needs to bind to a property of your top level view model which exposes your instance of PropertiesVm. A bit like:
public class TopLevelVm
{
public string ThresholdName { get; }
public PropertiesVm Properties { get; }
}
Then your XAML could be like:
<Expander Header="{Binding ThresholdName}" Content="{Binding }" />
Then the DataTemplate will pick the same DataContext, and your DataTemplate could look like this:
<Grid>
<TextBlock Text="{Binding ThresholdName}" />
<!-- Do something with .Properties -->
</Grid>

Related

Binding not working inside a <ItemsControl.ItemTemplate>

I can not get the binding of a text property for a DataTemplate in MVVM design pattern.
To show the problem I expose below a simplification of my problem, where I bind two different view properties to the same model property (aka AnObject.Text).
My code in MainWindow.xaml is:
...
<Button Grid.Row="0" Content="{Binding ButtonText}" />
...
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<Label Content="aaaaa" />
<TextBlock Text="{Binding ItemText}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
...
My code behind in MainWindow.xaml.cs (which sets the same DataContext for Button and every item in <ItemsControl ItemsSource>):
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
My code in MainWindowViewModel.cs is:
...
public ObservableCollection<object> MyItems => MyConverter.GetCollection(MyData.List);
public string ItemText => "dddd"; // this DOES works
public string ItemText => AnObject.Text; // this does NOT work
...
public string ButtonText => AnObject.Text; // this DOES works (note, same object property!)
...
Any idea why my binding inside the DataTemplate does not work?
Thanks in advance!
There are various things to understand here:
Button control will have the DataContext set to MainWindowViewModel instance. This is the reason why ButtonText variable value is getting reflected in Button control text.
For ItemsControl the DataContext is the the same as for the Button, i.e. the MainWindowViewModel instance.
Each item in the ItemsControl ItemsSource acts as a DataContext for the elements in the ItemTemplate, i.e. the DockPanel and its child elements. This is managed automatically by the framework. So essentially you will need a public property named ItemText in the class which will act as a DataContext for Dockpanel.
In your case the ItemText property is not the part of the objects which are in list.

Binding usercontrols Datacontext in DataTemplate

I have two usercontrols inside of a TabItem. The TabItem has it's own ViewModel, which has a property that the TabItem's child ContentControl's content bound to. This property represents another viewmodel, which will change the view depending on which one it is. Here's an example:
<TabItem DataContext="{Binding Path=MainLayerTabViewModel, Source={StaticResource ViewModelLocator}}" Header="Layers">
<ContentControl Content="{Binding ChildViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:LayersViewModel}">
<views:LayersTabView DataContext="{Binding ChildViewModel}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:UserDrawnLayersViewModel}">
<views:AlternateLayersTabView DataContext="{Binding ChildViewModel}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
Here's the view model used as the datacontext for the tabitem:
public class MainLayerTabViewModel : ViewModelBase
{
public object ChildViewModel { get; set; }
public MainLayerTabViewModel()
{
ChildViewModel = (App.Current.Resources["ViewModelLocator"] as ViewModelLocator).LayersViewModel;
}
}
Now, the two types of possible ViewModels for the ChildViewModel are LayersViewModel and UserDrawnLayersViewModel. When I change ChildViewModel to one of those, the view is properly switched via the DataTemplate. But the DataContext isn't actually being set. Nothing is being bound. I tried creating separate properties for each ViewModel in the MainLayerTabViewModel and binding the DataContext of each view to its own property, but that didn't work either.
I haven't verified this, however I can see couple of issue with your code.
View should be
<ContentControl Content="{Binding ChildViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:LayersViewModel}">
<views:LayersTabView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:UserDrawnLayersViewModel}">
<views:AlternateLayersTabView/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
ViewModel:
public class MainLayerTabViewModel : ViewModelBase
{
public ViewModelBase ChildViewModel { get; set; }
public MainLayerTabViewModel()
{
ChildViewModel = new LayersViewModel();
//or ChildViewModel = new UserDrawnLayersViewModel();
}
}
Hope that helps..

WPF Binding with DataContext on Custom Content Control

I have a custom wizard control WizardControl deriving from UserControl which has a dependency property called Pages with a data type of my custom class collection called WizardPageCollection.
The WizardControl is hosted in a Window with a view model called MainViewModel and the pages of the wizard instantiated using XAML.
I am trying to bind the pages to sub-view models Page1VM and Page2VM declared as properties on the MainViewModel.
The first page binding of DataContext to Page1VM works fine, however the binding of the second page fails with the following error message:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=Page2VM; DataItem=null; target element is 'MyPage' (Name=''); target property is 'DataContext' (type 'Object')
Q. Why does the binding work on the first page but fail on the second and is there a way I can get this to work whilst still keeping the MainViewModel declared within the DataContext XAML tags of MainWindow? I would prefer not to use the ViewModel as a dictionary resources as this has some implications for us which I won't go into detail about.
As suggested by a commentor, if I change the binding to use RelativeSource as follows:
<common:MyPage DataContext="{Binding DataContext.Page1VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<common:MyPage DataContext="{Binding DataContext.Page2VM, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
The first binding works ok, but the second one still fails, but with a different error message (as expected):
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.Page2VM; DataItem=null; target element is 'MyPage' (Name=''); target property is 'DataContext' (type 'Object')
Thanks for your time!
My code listing is shown below:
MainWindow XAML:
<Window.DataContext>
<common:MainViewModel />
</Window.DataContext>
<Grid>
<common:WizardControl>
<common:WizardControl.Pages>
<common:WizardPageCollection>
<common:MyPage DataContext="{Binding Page1VM}" />
<common:MyPage DataContext="{Binding Page2VM}" />
</common:WizardPageCollection>
</common:WizardControl.Pages>
</common:WizardControl>
</Grid>
MainViewModel and PageViewModel:
public class MainViewModel
{
public PageViewModel Page1VM
{
get;
set;
}
public PageViewModel Page2VM
{
get;
set;
}
public MainViewModel()
{
this.Page1VM = new PageViewModel("Page 1");
this.Page2VM = new PageViewModel("Page 2");
}
}
public class PageViewModel
{
public string Title { get; set; }
public PageViewModel(string title) { this.Title = title; }
}
WizardControl XAML:
<Grid>
<ContentPresenter Grid.Row="0" x:Name="contentPage"/>
</Grid>
WizardControl code-behind:
public partial class WizardControl : UserControl
{
public WizardControl()
{
InitializeComponent();
}
public WizardPageCollection Pages
{
get { return (WizardPageCollection)GetValue(PagesProperty); }
set { SetValue(PagesProperty, value); }
}
public static readonly DependencyProperty PagesProperty =
DependencyProperty.Register("Pages", typeof(WizardPageCollection), typeof(WizardControl), new PropertyMetadata(new WizardPageCollection(), new PropertyChangedCallback(Pages_Changed)));
static void Pages_Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
WizardPageCollection col = e.NewValue as WizardPageCollection;
WizardControl ctrl = obj as WizardControl;
ctrl.contentPage.Content = col.First();
}
}
public class WizardPageCollection : ObservableCollection<WizardPageBase> { }
public class WizardPageBase : ContentControl { }
MyPage XAML:
<Grid>
<Label Content="{Binding Title}" />
</Grid>
Your approach depends on the value inheritance of the Window's DataContext property, which doesn't work with your WizardPageCollection because it doesn't form a WPF element tree.
You should instead create your MainViewModel as a resource, and then reference it by StaticResource:
<Window ...>
<Window.Resources>
<common:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource MainViewModel}"/>
</Window.DataContext>
<Grid>
<common:WizardControl>
<common:WizardControl.Pages>
<common:WizardPageCollection>
<common:MyPage DataContext="{Binding Page1VM,
Source={StaticResource MainViewModel}}"/>
<common:MyPage DataContext="{Binding Page2VM,
Source={StaticResource MainViewModel}}"/>
</common:WizardPageCollection>
</common:WizardControl.Pages>
</common:WizardControl>
</Grid>
</Window>
#Clemens answer workarounds the problem but the problem is something else, imho.
When item is added to WizardPageCollection, it should be added to LogicalTree as well. Look at sources of ItemsControl for inspiration. It is definitelly possible to make your binding works as they were.
I would use viewmodel first approach here. Define pages as collection of page viewmodels and genereate the views. At the end the xaml would look like this:
<common:WizardControl PagesSource="{Binding Pages}">
<common:WizardControl.PageTemplate>
<DataTemplate>
<common:MyPage DataContext="{Binding }" />
</DataTemplate>
</common:WizardControl.PageTemplate>
</common:WizardControl>
alternativelly, consider your WizardControl derive from Selector class instead usercontrol. (Selector is base class from listbox. It has itemssource and selected item).
<common:WizardControl ItemsSource="{Binding Pages}"
SelectedItem="{Binding SelectedPage}">
<common:WizardControl.ItemTemplate>
<DataTemplate>
<common:MyPage DataContext="{Binding }" />
</DataTemplate>
</common:WizardControl.ItemTemplate>
</common:WizardControl>

ToolBar item DataTemplate binding RelativeSource search failing to find parent ToolBar

I have a ToolBar containing Buttons, some of the Buttons have only an Image for content, others have only Text. I am trying to bind the width property of the Button Image to a custom Property on my derived ToolBar class. It works sometimes but fails other times with the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='NuiWpfCore.Controls.ToolBar', AncestorLevel='1''. BindingExpression:Path=IconSize; DataItem=null; target element is 'Image' (Name=''); target property is 'Width' (type 'Double')
Here is the xaml containing the element binding that is failing. The DataTemplate is returned from a DataTemplateSelector which is created inline:
<pres:ToolBar x:Class="NuiWpfCore.Controls.ToolBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:pres="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:core="clr-namespace:NuiWpfCore"
xmlns:ctrls="clr-namespace:NuiWpfCore.Controls"
xmlns:select="clr-namespace:NuiWpfCore.Selectors"
xmlns:converters="clr-namespace:NuiWpfCore.Converters"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
<ToolBar.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/NuiWpfCore;component/Controls/MenuBarTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:ListPairToStringConverter x:Key="ListPairToStringConverter" />
<converters:IconMetaDataToImageConverter x:Key="IconMetaDataToImageConverter" />
<converters:IconMetaDataToImageConverterParameter x:Key="IconToImageConverterParameter"
ConvertToImage="False" Width="16" Height="16" />
</ResourceDictionary>
</ToolBar.Resources>
<ToolBar.ItemTemplateSelector>
<select:ToolBarItemDataTemplateSelector>
<!-- other DataTemplates omitted for brevity -->
<select:ToolBarItemDataTemplateSelector.IconCommand>
<DataTemplate DataType="{x:Type core:PropertyElement}">
<Button IsEnabled="{Binding Path=CanEdit}" Command="{Binding}">
<Button.Content>
<Image
Width="{Binding Path=IconSize,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ctrls:ToolBar}} }"
Height="{Binding Path=Width,
RelativeSource={RelativeSource Self}}"
Source="{Binding Path=MetaData,
Converter={StaticResource IconMetaDataToImageConverter},
ConverterParameter={StaticResource IconToImageConverterParameter}}"/>
</Button.Content>
</Button>
</DataTemplate>
</select:ToolBarItemDataTemplateSelector.IconCommand>
<!-- other DataTemplates omitted for brevity -->
</select:ToolBarItemDataTemplateSelector>
</ToolBar.ItemTemplateSelector>
</pres:ToolBar>
Here is the ToolBar class with the Source Property for the binding.
public partial class ToolBar : System.Windows.Controls.ToolBar, Views.IView
{
public ToolBar() : base()
{
InitializeComponent();
IconSize = 32;
}
public int IconSize { get; set; }
}
This ToolBar class is sometimes used in a ToolBarTray and other times it is not, but the bind search fails in both cases in certain scenarios.
Does anybody have any ideas as to why this might be failing?
Have you considered making IconSize on your ToolBar an inherited property?
public static readonly DependencyProperty IconSizeProperty =
DependencyProperty.RegisterAttached( "IconSize", typeof(double), typeof(ToolBar ),
new FrameworkPropertyMetadata(32, FrameworkPropertyMetadataOptions.Inherits));
public static double GetIconSize(DependencyObject target)
{
return (double)target.GetValue(IconSizeProperty);
}
public static void SetIconSize(DependencyObject target, double value)
{
target.SetValue(IconSizeProperty, value);
}
Then you can just access the IconSize like
<Button.Content>
<Image
Width="{Binding RelativeSource={RelativeSource Self}, ctrls::ToolBar.IconSize}"
Height="{Binding Path=Width,RelativeSource={RelativeSource Self}}"
Source="{Binding Path=MetaData,
Converter={StaticResource IconMetaDataToImageConverter},
ConverterParameter={StaticResource IconToImageConverterParameter}}"/>
First you should set it on your toolbar, and every other element down the tree can access this property.
Sorry out of my head, not 100% guarenteed to be correct. But the overall idea of
Value Inheritance is a good way to solve this.
The DataTemplate looks like it is being defined inside a DataTemplateSelector declaration, which isn't part of the Visual Tree, and so won't be able to navigate up from there if the Binding were being evaluated in that spot. Where is the template actually being applied?

WPF binding to Listbox selectedItem

Can anyone help with the following - been playing about with this but can't for the life of me get it to work.
I've got a view model which contains the following properties;
public ObservableCollection<Rule> Rules { get; set; }
public Rule SelectedRule { get; set; }
In my XAML I've got;
<ListBox x:Name="lbRules" ItemsSource="{Binding Path=Rules}"
SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox x:Name="ruleName">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Now the ItemsSource works fine and I get a list of Rule objects with their names displayed in lbRules.
Trouble I am having is binding the SelectedRule property to lbRules' SelectedItem. I tried binding a textblock's text property to SelectedRule but it is always null.
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
The error I'm seeing in the output window is:
BindingExpression path error: 'SelectedRule' property not found.
Can anyone help me with this binding - I can't see why it shouldn't find the SelectedRule property.
I then tried changing the textblock's text property as bellow, which works. Trouble is I want to use the SelectedRule in my ViewModel.
<TextBlock Text="{Binding ElementName=lbRules, Path=SelectedItem.Name}" />
Thanks very much for your help.
First off, you need to implement INotifyPropertyChanged interface in your view model and raise the PropertyChanged event in the setter of the Rule property. Otherwise no control that binds to the SelectedRule property will "know" when it has been changed.
Then, your XAML
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
is perfectly valid if this TextBlock is outside the ListBox's ItemTemplate and has the same DataContext as the ListBox.
Inside the DataTemplate you're working in the context of a Rule, that's why you cannot bind to SelectedRule.Name -- there is no such property on a Rule.
To bind to the original data context (which is your ViewModel) you can write:
<TextBlock Text="{Binding ElementName=lbRules, Path=DataContext.SelectedRule.Name}" />
UPDATE: regarding the SelectedItem property binding, it looks perfectly valid, I tried the same on my machine and it works fine. Here is my full test app:
XAML:
<Window x:Class="TestWpfApplication.ListBoxSelectedItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxSelectedItem" Height="300" Width="300"
xmlns:app="clr-namespace:TestWpfApplication">
<Window.DataContext>
<app:ListBoxSelectedItemViewModel/>
</Window.DataContext>
<ListBox ItemsSource="{Binding Path=Rules}" SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Code behind:
namespace TestWpfApplication
{
/// <summary>
/// Interaction logic for ListBoxSelectedItem.xaml
/// </summary>
public partial class ListBoxSelectedItem : Window
{
public ListBoxSelectedItem()
{
InitializeComponent();
}
}
public class Rule
{
public string Name { get; set; }
}
public class ListBoxSelectedItemViewModel
{
public ListBoxSelectedItemViewModel()
{
Rules = new ObservableCollection<Rule>()
{
new Rule() { Name = "Rule 1"},
new Rule() { Name = "Rule 2"},
new Rule() { Name = "Rule 3"},
};
}
public ObservableCollection<Rule> Rules { get; private set; }
private Rule selectedRule;
public Rule SelectedRule
{
get { return selectedRule; }
set
{
selectedRule = value;
}
}
}
}
Yocoder is right,
Inside the DataTemplate, your DataContext is set to the Rule its currently handling..
To access the parents DataContext, you can also consider using a RelativeSource in your binding:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ____Your Parent control here___ }}, Path=DataContext.SelectedRule.Name}" />
More info on RelativeSource can be found here:
http://msdn.microsoft.com/en-us/library/system.windows.data.relativesource.aspx
For me, I usually use DataContext together in order to bind two-depth property such as this question.
<TextBlock DataContext="{Binding SelectedRule}" Text="{Binding Name}" />
Or, I prefer to use ElementName because it achieves bindings only with view controls.
<TextBlock DataContext="{Binding ElementName=lbRules, Path=SelectedItem}" Text="{Binding Name}" />
There is a shorter version to bind to a selected item's property:
<TextBlock Text="{Binding Rules/Name}" />
since you set your itemsource to your collection, your textbox is tied to each individual item in that collection. the selected item property is useful in this scenario if you were trying to do a master-detail form, having 2 listboxes. you would bind the second listbox's itemsource to the child collection of rules. in otherwords the selected item alerts outside controls that your source has changed, internal controls(those inside your datatemplate already are aware of the change.
and to answer your question yes in most circumstances setting the itemsource is the same as setting the datacontext of the control.

Resources