Binding to ViewModel instance from View - wpf

I have program which initializes PersonsViewModel in MainWindowViewModel's constructor.
public MainWindowViewModel()
{
PersonsViewModel viewModel = new PersonsViewModel();
}
In MainWindow.xaml, PersonsViewModel and PersonsView are connected.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:PersonsViewModel}">
<vw:PersonsView />
</DataTemplate>
</Window.Resources>
I use viewModel as ItemsControl ItemsSource.
<ItemsControl ItemsSource="{Binding viewModel}" Margin="4" />
Now my program opens UserControl and I need to set instance of PersonsViewModel to UserControl.DataContext.
<UserControl.DataContext>
<vm:PersonsViewModel />
</UserControl.DataContext>
Am I creating a new instance of PersonsViewModel. If I am doing so, then how I can bind it to PersonsViewModel instance? Because I have following code in UserControl. I have PersonsList bound to ItemsSource and I need to bind Command to PersonsViewModel instance.
<ItemsControl ItemsSource="{Binding PersonsList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="50" Text="{Binding Name}" />
<Button Content="Ok" Width="20" Margin="3" Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}, Path=DataContext.Command}" CommandParameter="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

You can remove the UserControl.DataContext and Add ItemsControl.ItemTemplate. Since each ItemsControl is bound to List of person viewmodel. Each item will get the viewmodel as its datacontext. Refer the below code.
<ItemsControl ItemsSource="{Binding ViewModels}" Margin="4">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:PersonsViewModel}">
<vw:PersonsView />
</DataTemplate>
</ItemsControl.ItemTemplate>

Related

why my Listbox data binding using DataContext is not working?

I am new to WPF and data binding,
here I have a Listbox which works just fine:
<ListBox
x:Name="listBox" ItemsSource="{Binding Path=AllFriends}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now I want to try DataContext so I use:
<UserControl.Resources>
<CollectionViewSource
x:Key="FriendsGroups"
Source="{Binding Path=AllFriends}"
>
</CollectionViewSource>
</UserControl.Resources>
<ListBox
x:Name="listBox"
DataContext="{StaticResource FriendsGroups}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But this one is not working.
I would appreciate if you could tell me why?
Even when you use a CollectionViewSource, you still have to set the ItemsSource property, not the DataContext:
<ListBox ItemsSource="{Binding Source={StaticResource FriendsGroups}}">

How to find datacontext of parent's parent for dynamic control

We have following xaml code to display context menu and fire command when delete menu is clicked.
For the context menu item, I would like to bind command which is present in DataContext of ItemsControl. We tried with RelativeSource={RelativeSource TemplatedParent} but as the tree is created dynamically (using dataContext) it is not able to find DeleteCommand.
<Grid x:Name="MyGrid" >
<ItemsControl ItemsSource="{Binding Path=TreeRoot.Children}">
<ItemsControl.ItemTemplate>
<HierarchicalDataTemplate>
<StackPanel>
<Label Background="Black" Content="{Binding Path=DisplayText}"/>
<TreeView ItemsSource="{Binding Converter={StaticResource myCompositeNodeConverter}}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:CompositeNode}" ItemsSource="{Binding Path=Children}" >
<TextBlock Text="{Binding Path=DisplayText}" Foreground="Black"></TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:LeafNode}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=DisplayText}"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ContextMenu>
<ContextMenu >
<MenuItem Header="Delete" Command="{Binding DeleteCommand}" />
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
</StackPanel>
</HierarchicalDataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
How do we bind DeleteCommand for context menu item to DataContext of ItemsControl?

WPF binding Listbox layoutpanel

I´m using devexpress and I want to do a binding with a Listbox, but I have an error. Here my code:
<ListBox x:Name="_list" >
<ListBox.ItemTemplate>
<DataTemplate>
<dxd:LayoutPanel
Caption="{Binding nameList}"
AllowHide ="False" AllowFloat="False"
GotFocus="panel_GotFocus" >
<TextBox Text="Hello" />
</dxd:LayoutPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With this code, Caption {Binding nameList} is empty.
I have tried this.
<ListBox x:Name="_list" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBox Text="{Binding nameList}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In this case, text in TextBox is correct, I need to use the first code.
You seem to be a bit confused about how to use this ListBox. First, you need a collection property to bind to the ListBox.ItemsSource property. Let's say you have a collection of User objects called Users:
<ListBox ItemSource="{Binding Users}" />
Now we want to define how each User should be displayed... all the properties of the User class will be available to the Binding inside the DataTemplate. Let's say that the User class has two properties; Name and Age:
<DataTemplate DataType="{x:Type YourDataTypeNamespace:User}">
<StackPanel>
<TextBox Text="{Binding Name}" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
Then putting it all together:
<ListBox ItemSource="{Binding Users}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type YourDataTypeNamespace:User}">
<StackPanel>
<TextBox Text="{Binding Name}" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

binding a command inside a listbox item to a property on the viewmodel parent

I've been working on this for about an hour and looked at all related SO questions.
My problem is very simple:
I have HomePageVieModel:
HomePageVieModel
+IList<NewsItem> AllNewsItems
+ICommand OpenNews
My markup:
<Window DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Path=OpenNews}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The list shows fine with all the items, but for the life of me whatever I try for the Command won't work:
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel, AncestorLevel=1}}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=FindAncestor}**}">
<Hyperlink Command="{Binding Path=OpenNewsItem, RelativeSource={RelativeSource AncestorType=vm:HomePageViewModel,**Mode=TemplatedParent}**}">
I just always get :
System.Windows.Data Error: 4 : Cannot find source for binding with reference .....
Update
I am setting my ViewModel like this? Didn't think this would matter:
<Window.DataContext>
<Binding Path="HomePage" Source="{StaticResource Locator}"/>
</Window.DataContext>
I use the ViewModelLocator class from the MVVMLight toolkit which does the magic.
Slightly different example but,
I found that by referencing the parent container (using ElementName) in the binding you can get to it's DataContext and its subsequent properties using the Path syntax. As shown below:
<ItemsControl x:Name="lstDevices" ItemsSource="{Binding DeviceMappings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ComboBox Text="{Binding Device}" ItemsSource="{Binding ElementName=lstDevices, Path=DataContext.AvailableDevices}" />
...
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There's two issue working against you here.
The DataContext for the DataTemplate is set to the item the template is displaying. This means you can't just use binding without setting a source.
The other issue is that the template means the item is not technically part of the logical tree, so you can't search for ancestors beyond the DataTemplate node.
To solve this you need to have the binding reach outside the logical tree. You can use a DataContextSpy defined here.
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.Resources>
<l:DataContextSpy x:Key="dataContextSpy" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Source={StaticResource dataContextSpy}, Path=DataContext.OpenNews}" CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
try something like this
<Button Command="{Binding DataContext.YourCommand,RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
he can't find your command binding inside the listbox because you set a diffrent datacontext than the viewmodel for that listbox
So looks like you are trying to give the proper DataContext to the HyperLink so as to trigger ICommand.
I think a simple element name binding can solve this.
<Window x:Name="window" DataContext="{Binding HomePageViewModel../>
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink DataContext="{Binding DataContext ,ElementName=window}" Command="{Binding Path=OpenNews}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
The AncestorType checks only for Visual-Types not for ViewModel types.
Well, it's a little bit late, I know. But I have only recently faced the same problem. Due to architectural reasons I decided to use a static viewmodel locator instead of the dataContextSpy.
<UserControl x:Class="MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locator="clr-namespace: MyNamespace"
DataContext="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel}}" >
<ListBox ItemsSource="{Binding Path=AllNewsItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding Source={x:Static locator:ViewModelLocator.MyViewModel},
Path=OpenNews}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding Path=NewsContent}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
The static viewmodel locator instantiates the view model:
namespace MyNamespace
{
public static class ViewModelLocator
{
private static MyViewModelType myViewModel = new MyViewModelType();
public static MyViewModelType MyViewModel
{
get
{
return myViewModel ;
}
}
}
}
Using this workaround is another way to bind from a data template to a command that is in the viewmodel.
The answer from #Darren works well in most cases, and should be the preferred method if possible. However, it is not a working solution where the following (niche) conditions all occur:
DataGrid with DataGridTemplateColumn
.NET 4
Windows XP
...and possibly in other circumstances too. In theory it should fail on all versions of Windows; but in my experience the ElementName approach works in a DataGrid on Windows 7 upwards but not XP.
In the following fictional example, we are trying to bind to an ICommand called ShowThingCommand on the UserControl.DataContext (which is the ViewModel):
<UserControl x:Name="ThisUserControl" DataContext="whatever...">
<DataGrid ItemsSource="{Binding Path=ListOfThings}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Thing">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding ElementName=ThisUserControl, Path=ShowThingCommand}"
CommandParameter="{Binding Path=ThingId}"
Content="{Binding Path=ThingId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
Due to the DataTemplate not being in the same VisualTree as the main control, it's not possible to reference back up to the control by ElementName.
To solve this, the little known .NET 4 and above {x:Reference} can be used. Modifying the above example:
<UserControl x:Name="ThisUserControl" DataContext="whatever...">
<DataGrid ItemsSource="{Binding Path=ListOfThings}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Thing">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding Source={x:Reference ThisUserControl}, Path=ShowThingCommand}"
CommandParameter="{Binding Path=ThingId}"
Content="{Binding Path=ThingId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
For reference, see the following stackoverflow posts:
Question 19244111
Question 5834336
...and for an explanation of why ElementName doesn't work in this circumstance, see this blog post which contains a pre-.NET 4 workaround.
<ListBox xmlns:model="clr-namespace:My.Space;assembly=My.Assembly"
ItemsSource="{Binding Path=AllNewsItems, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Hyperlink Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.(model:MyNewsModel.OpenNews), Mode=OneWay}"
CommandParameter="{Binding Path=., Mode=OneWay}">
<TextBlock Text="{Binding Path=NewsContent, Mode=OneWay}" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

MVVM Binding in Silverlight3 ItemsControl to get the parent controls DataContext

I have the following ItemsControl in Silverlight 3.
<ItemsControl ItemsSource="{Binding ValueCollectionList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="MyBtn" Height="40" Content="{Binding Name}"
Tag="{Binding Value}"
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot, Path=ButtonCommand}"
cmd:ButtonBaseExtensions.CommandParameter="{Binding ElementName=MyBtn, Path=Tag}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The Problem is that I have the ItemsControl bound to the Collection in my ViewModel, but I need the button to trigger a command on the ViewModel which is of course not Available in the DataContext of the button since it only contains the collection.
I can make the command fire by setting my ViewModel as a Resource and then binding to it as a StaticResource, but I want to know why the element to element binding won't work in this scenario. I would prefer not to use the StaticResource binding because that requires the default constructor on the ViewModel and so I can't inject my data easily.
UPDATE
I'm working through this slowly... Looking at the suggestions from Peter I realized that I may have more serious binding issues because of my page setup. I have two possible road blocks, but first things first.
My Items control above is wrapped in another items control that is bound to an observable collection. I moved my items control so that its a direct child of the root items control. It was wrapped in another control that I'll get to. So I tried the element binding to the items control ControlItemList, but its a collection so it can't find my ButtonCommand method in that Collection. What I need to do is bind to an item within that collection. How do I bind to a single item within the collection?
<Grid x:Name="LayoutRoot" Background="White"><!-- DataContext="{Binding Path=., Source={StaticResource lvvm}}">-->
<StackPanel Orientation="Vertical">
<ItemsControl x:Name="ControlItemList" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="100" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="ControlName" Grid.Column="0" Text="{Binding Name}" VerticalAlignment="Center" />
<ItemsControl ItemsSource="{Binding ValueCollectionList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
So, Assuming I can get the above to work the other road block is that my items control is wrapped in another usercontrol that I'm using to get DataTemplateSelector type functionality. I think this control may be blocking me from getting to the parent DataContext. Is there a limit as to how far up the tree you can go?
<common:DeviceControlTemplateSelector Grid.Column="1" FieldType="{Binding ValueType}" Margin="0,2,0,2">
<common:DeviceControlTemplateSelector.StringTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay}" Width="100"/>
</DataTemplate>
</common:DeviceControlTemplateSelector.StringTemplate>
<common:DeviceControlTemplateSelector.DateTimeTemplate>
<DataTemplate>
<TextBox Text="this is date time binding" Width="100"/>
</DataTemplate>
</common:DeviceControlTemplateSelector.DateTimeTemplate>
<common:DeviceControlTemplateSelector.BooleanTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Value, Mode=TwoWay}" />
</DataTemplate>
</common:DeviceControlTemplateSelector.BooleanTemplate>
<common:DeviceControlTemplateSelector.IntegerTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ValueCollection}" DisplayMemberPath="Value" SelectedIndex="{Binding Value, Mode=TwoWay}" >
</ComboBox>
</DataTemplate>
</common:DeviceControlTemplateSelector.IntegerTemplate>
<common:DeviceControlTemplateSelector.MultiButtonTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding ValueCollectionList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="MyBtn"
Height="40" Content="{Binding Name}"
Tag="{Binding Value}"
cmd:ButtonBaseExtensions.Command="{Binding ElementName=ControlItemList, Path=DataContext.ButtonCommand}"
cmd:ButtonBaseExtensions.CommandParameter="{Binding Value}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</common:DeviceControlTemplateSelector.MultiButtonTemplate>
<Button x:Name="MyBtn"
Height="40" Content="{Binding Name}"
Tag="{Binding Value}"
cmd:ButtonBaseExtensions.Command="{Binding ElementName=ControlItemList, Path=ButtonCommand}"
cmd:ButtonBaseExtensions.CommandParameter="{Binding Value}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks again everyone for your help!
the best solution i have found so far to deal with this situation is by Dan Wahl where he uses a DataContextProxy.
http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx
Try to change
from
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot, Path=ButtonCommand}"
to
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot, Path=DataContext.ButtonCommand}"
OR
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot.DataContext, Path=ButtonCommand}"
In MVVM messaging is a strong concept for cummunication between ViewModels. You could use PRISM Eventaggregator or the Messenger class of MVVM Light Toolkit. On the button command you can publish a message and subscribe to it in the viewmodel.

Resources