Binding not working inside a <ItemsControl.ItemTemplate> - wpf

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.

Related

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..

How to choose View dynamically based on current DataContext view model

I have a Page which will receive a different DataContext (View Model), dynamically.
I can't figure out how to use DataTemplate in a switch/case fashion, to render the appropriate view based on the current context.
I would imagine that I will have multiple DataTemplates like this:
<DataTemplate DataType="{x:Type LocalViewModels:ABC}">
<LocalViews:ABC/>
</DataTemplate>
but can't figure out in what container to put them. Only one of them will be rendered at a time, so ListBox makes no sense to me.
Given the following XAML of a Window
<Window.Resources>
<DataTemplate DataType="{x:Type local:ABC}">
<Border BorderThickness="2" BorderBrush="Red">
<TextBlock Text="{Binding Text}"/>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding}"/>
</StackPanel>
you can simply assign an instance of ABC to the Window's DataContext to create the templated view.
class ABC
{
public string Text { get; set; }
}
...
public MainWindow()
{
InitializeComponent();
DataContext = new ABC { Text = "Hello, World." };
}
All details are here: Data Templating Overview.

Referencing a UIElement in a ViewModel from XAML

I'm relatively new to using WPF and the MVVM architecture. I have a question about referencing UIelements from a XAML window's DataContext.
I have menu items that are bound to Views DataContext using this syntax:
<MenuItem Header="About" Command="{Binding AboutCommand}" />
I'd like to use a similar paradigm to add items to a grid. Right now I am using a class WorkflowDesigner. I can add it to my grid using the following code in my ViewModel:
grid.AddChildren(wd.View)
where view is of type UIElement.
What I'd rather do is add is reference to it from my XAML file without putting anything in my codebehind so that I can use the XAML mostly as a skin. Is it possible to use a tag just takes its UIElement from the datacontext of the XAML file?
This is possible, but it's not within the spirit of MVVM to have your ViewModel provide controls to your view. Ideally your ViewModel should have no dependencies on System.Windows.Controls at all.
If you must, then you can use a ContentControl:
<ContentControl Content={Binding wd.View} />
To handle this I'd create a ViewLocator class and put an instance of it into your resource dictionary. Then use this:
<ContentControl Content={Binding Source={StaticResource ViewLocator}, Path=WorkflowDesigner} />
I'm not sure if I quite understand your problem, but if you have a class you wish to present in your view from your ViewModel, you could use an ItemsControl to display different classes using a DataTemplate.
Say you have class User
public class User
{
public string Id { get; set;}
public string Name { get; set;}
}
public class UserViewModel
{
private ObservableCollectionaUser<User> _users = new......
public ObservableCollection<User> Users
{
get
{
return _users;
}
}
}
In UserView, you could have
<ItemsControl ItemsSource="{Binding Users}">
<ItemsControl.Resources>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
This way, a User would be presented in the view using the template declared above. Then you would not have to use UIElements in your ViewModel.
The ItemsControl could refer to grid items, and have items presented in a grid with SharedGridScope (if I remember correctly).

WPF MVVM - Binding properties on UserControls to ViewModel of container

I have a Window (MainWindow.xaml) which has a ViewModel (MainWindowViewModel.cs). I also have a UserControl called MyUserControl.xaml which also has a corresponding ViewModel (MyUserControlViewModel.cs).
I have inserted two instances of the UserControl into the MainWindow:
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject"
Title="My Window">
<Grid>
<local:MyUserControl Visibility="{Binding Path=MyUserControl1Visibility, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
<local:MyUserControl Visibility="{Binding Path=MyUserControl2Visibility, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
In the CodeBehind of MainWindow I set the DataContext of the Window to an instance of the ViewModel:
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
MainWindowViewModel has the Visibility-properties that the MyUserControl-instances are bound to. They both look like this:
private Visibility _myUserControl1Visibility = Visibility.Collapsed;
public Visibility MyUserControl1Visibility
{
get
{
return _myUserControl1Visibility;
}
private set
{
if (value != _myUserControl1Visibility)
{
_myUserControl1Visibility = value;
OnPropertyChanged("MyUserControl1Visibility");
}
}
}
Furthermore, MainWindow and MainWindowViewModel has buttons and commands to enable the user to toggle between the two MyUserControl instances. That is, only one of them are shown at any time.
This worked fine… until the UserControls got their own ViewModels. Now the runtime tries to find the bound VisibilityProperties (MyUserControl1Visibility…) on the ViewModels of the UserControls instead of the ViewModel of MainWindow.
How do I make these bindings go to MainWindowViewModel instead of to the respective ViewModels of the UserControl instances?
The MyUserControls inherited the DataContext from MainWindow and that's why it works to begin with. When MyUserControl1 get a ViewModel of its own that will be its DataContext. Maybe this was clear to you :) To use the DataContext of the MainWindow in the binding instead you can use RelativeSource or name the Window and use ElementName
<local:MyUserControl Visibility="{Binding ElementName=mainWindow,
Path=DataContext.MyUserControl1Visibility,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Visibility="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=DataContext.MyUserControl1Visibility}"

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