Binding to viewmodel's property from a template - wpf

I made a view for my GameViewModel
I have some xaml like that
<UserControl.Resources>
<DataTemplate DataType="{x:Type ViewModels:PlayerViewModel}">
<StackPanel>
<Button
Content="{Binding ?????}"
Command="{Binding WrongAnswerCommand}" />
<TextBlock
Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Players}"
IsSynchronizedWithCurrentItem="True">
</ListBox>
</Grid>
So, here it is an observable collection Players.
I need the button content to be binded to GameViewModel's property.
What should I use?

Basically, you need to go up to a higher level to get the full view model.
Something like
Content="{Binding DataContext.MyContentPropertyName,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}"
Or
Content="{Binding DataContext.MyContentPropertyName,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
Where MyContentPropertyName is the property in GameViewModel that represents the content of the button.
Note: from the snippet it seems that both bindings will produce the same result, adding them both to make an example that you can choose how high you want to go to find the right context.

Related

Wpf Expander Collection, how to have parent ViewModel know which Expander object current?

In a view which contains an item having a collection of child items, I have an ItemsControl which hosts the child items collection. The individual items are contained in an Expander. When a child item is expanded, I need the parent view model to be aware of which child is being acted upon. I was able to implement an Event Trigger which passes the child object as a parameter to the parent view model, and the parent view model can then set a SelectedChildObject property. That is what I need but where it falls short is when multiple items are expanded, and the user acts on an item which is not in the most recently expanded item. When this happens the item they are interacting with does not match the SelectedChildObject property, since only the most recently expanded object would be the property value.
I have seen the solutions which use a ListBox to contain the Expander, and then set the Expander IsExpanded based on the ListBox IsSelected, but I don't like this solution because it only allows one expander open at a time, plus it does not seem possible to have the Expander stretch to fill all of the space in the ListBox, and this look does not look good.
Is there a way I can always let the parent view model know which child object is being acted upon?
<ItemsControl Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding Slots, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Padding="10">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding DataContext.ExpandedCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0"
Height="16" Width="16" Source="/WpfApp1;component/Assets/Images/Warning.ico" />
<StackPanel Grid.Row="0" Grid.Column="1"
Orientation="Horizontal">
<Label Content="Slot: " />
<Label Content="{Binding SlotNumber, Mode=TwoWay, ValidatesOnNotifyDataErrors=False}" />
</StackPanel>
</Grid>
</Expander.Header>
<StackPanel Margin="20">
<StackPanel Orientation="Horizontal">
<Label Content="Slot Number:" Margin="0 5 2 0" Width="100" />
<TextBox Text="{Binding SlotNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}"
Style="{DynamicResource Configuration.Input.TextBox}"
Width="30" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Modules:" Margin="0 5 2 0" Width="100" />
<ListBox ItemsSource="{Binding Source={x:Static local:SlotsViewModel.AllowedModules}}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"
Command="{Binding DataContext.AddRemoveAllowedModuleCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What kind of actions are you trying to perform on Slots?
Something like removing the last manipulated slot? If so then you will probably have to write a behavior or a control that will capture PreviewMouseDown events and send a command/event to notify your parent View Model. But it's not ideal.
And any way you will probably want the "Selected slot" to be highlighted befor it can be removed. You could also try to remove background of the expander and use the ListBox. So that when you click on the background of your Slot it selects and highlights the ListBoxItem. In this way you can bind SelectedItem to the VM.

Binding to viewmodel from inside a datatemplate

I have multiple videos displayed they are bound with a videocollection in Mainviewmodel. Everything works fine untill I try to bind the enter command to Mainviewmodel. I Don't know the syntax for this. As it stands the binding is set to Video and not Mainviewmodel.
Errormessage:
'StartVideoCommand' property not found on 'object' ''Video'
Xaml:
<Window.Resources>
<local:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
<ListBox ItemsSource="{Binding Videos}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.InputBindings>
!!! <KeyBinding Key="Enter" Command="{Binding StartVideo}" /> !Bound to Video not to Mainviewmodel grrr
</Grid.InputBindings>
... layout stuff
<TextBlock Text="{Binding Title}" Grid.Column="0" Grid.Row="0" Foreground="White"/>
<TextBlock Text="{Binding Date}" Grid.Column="0" Grid.Row="1" Foreground="White" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Length}" Grid.Column="1" Grid.Row="1" Foreground="White" HorizontalAlignment="Right"/>
... closing tags
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.StartVideo}"
Another approach would be to use ElementName binding instead of RelativeSource.
Example:
<Window x:Name="root" ... >
...
Command="{Binding ElementName=root, Path=DataContext.StartVideo}"
...
A possible advantage over RelativeSource is that this is explicit; if someone changes the XAML hierarchy then relative references could break unintentionally. (Not likely in this specific example of binding to a Window however).
Also if your "root" element already is named then so much the better it is easy to take advantage of.
It is also somewhat more readable.

Bind to other element in a stackpanel without name

I'm creating a DataTemplate in WPF, so it won't compile if I use x:Name. This is the relevant part of my code:
<DataTemplate>
<StackPanel>
<Image .../>
<Textbox Width={Binding to image?, Path=ActualWidth} />
</StackPanel>
</DataTemplate>
How can I bind to the Image without the use of name?
You can use RelativeSource:
<DataTemplate>
<StackPanel>
<Image Source="{Binding Photo}"/>
<TextBox Width="{Binding Path=Children[0].ActualWidth, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel, AncestorLevel=1}}" />
</StackPanel>
</DataTemplate>
You can't directly binding to Image but you can do that with StackPanel and property Children.

Silverlight pass DataContext to ConverterParameter?

How can I pass the DataContext of LayoutRoot to the converter of the ContentControl inside the ListBox items template?
<Grid x:Name="LayoutRoot"
Background="White"
DataContext="{Binding Source={StaticResource myViewModel}}">
<StackPanel HorizontalAlignment="Left"
Margin="6,6,0,394"
Orientation="Vertical"
Width="200"
d:LayoutOverrides="Height">
<ListBox x:Name="listBox2"
ItemsSource="{Binding MyCollection, Mode=TwoWay}"
VerticalAlignment="Top"
Height="400">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"
ContentTemplate="{Binding Converter={StaticResource myConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding Value1, Mode=TwoWay}"/>
</StackPanel>
</Grid>
I want to be able to touch the objects inside the DataContext from within the Converter and use them for TwoWay binding on controls within the ListBox item's DataTemplate.
Any ideas? Any suggestions?
Thank you.
I just got the DataContext up to the converter using:
<ContentControl Content="{Binding}"
ContentTemplate="{Binding Converter={StaticResource stringToDataTemplateConverter}, ConverterParameter={StaticResource myViewModel}}" />
Now I have another problem my dynamic property binding is not working.
(sorry for my bad english)
I'm not sure about what you are trying to do here but with SL 5 you can use RelativeSource to get the DataContext:
{Binding DataContext,RelativeSource={RelativeSource AncestorLevel=1,AncestorType=Grid}}

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