How to bind to another function, inside a container with ItemsSource [duplicate] - c#

I have an ItemsControl whose for the ItemTemplate DataTemplate contains a Button. I want the Command on the button to bind to a Command on the DataContext of the ItemsControl, not the ItemTemplate. I think the solution has to do with using RelativeSource, but my attempts so far have failed:
<ItemsControl ItemsSource="{Binding Games}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Path=GameSelectedCommand, Source={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}"
Style="{StaticResource MenuButtonStyle}"
Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How can I get the Button to bind to the GameSelectedCommand of the ItemsControl's DataContext object?

You're setting the source of the binding to the ItemsControl itself. Therefore, you'll need to dereference the DataContext of the ItemsControl:
Command="{Binding DataContext.GameSelectedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
How would you have known this? Take a look at your debug output window when running the app. You'll see a message along the lines of "Cannot resolve property 'GameSelectedCommand' on type 'ItemsControl'".

Related

Adding new element to a ComboBox which is in a DataGrid in WPF using MVVM

I have a DataGrid which contains Transactions. I have an InterestOrDividend column where I can select a value using a ComboBox. This works fine.
A new feature would be to enter a value and add it to the list of possibilities. I set IsEditable to true and added Interaction.Triggers from http://schemas.microsoft.com/expression/2010/interactivity
Problem 1:
It seems InterestOrDividendSelectionChangedCommand not only fires when the selection is changed but also when I scroll the DataGrid and such rows come into view which has a not null value in the InterestOrDividend column. Moreover, when a new value is entered (which is not in the list), the event does not fire.
Problem 2:
I want to bind the Text property of the ComboBox to get the newly added value. It seems the event fire before the Text property changes, so I get the old value.
<DataGridTemplateColumn Header="{x:Static r:Resource.InterestOrDividend}"
CellTemplate="{StaticResource InterestOrDividendEditingTemplate}"
CellEditingTemplate="{StaticResource InterestOrDividendEditingTemplate}" />
<DataTemplate x:Key="InterestOrDividendEditingTemplate">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.AppData.AlienTypeObjects}"
SelectedItem="{Binding InterestOrDividend}"
DisplayMemberPath="FullName"
IsEditable="True"
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}},
Path=DataContext.InterestOrDividendSelectionChangedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ComboBox}}, Path=Text}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
This is my solution. Instead of using the EventTrigger I catch the new element in the setter of NewInterestOrDividend. It is important that the UpdateSourceTrigger is LostFocus. When InterestOrDividend is null and you change the focus then value in NewInterestOrDividend contains the new value.
<DataTemplate x:Key="InterestOrDividendEditingTemplate">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.AppData.AlienTypeObjects}"
DisplayMemberPath="FullName" Style="{StaticResource ComboBoxError}"
IsEditable="True"
SelectedItem="{Binding InterestOrDividend, UpdateSourceTrigger=LostFocus}"
Text="{Binding NewInterestOrDividend, UpdateSourceTrigger=LostFocus}">
</ComboBox>
</DataTemplate>

Is there a way to pass treeViewItem name as command parameter?

I am creating a simple treeview with binding to a collection. What I want is to display full info about the collection element in a listview when treeview element is selected. Similar to windows file explorer. The problem is I cannot bind selected treeviewitem to a Command.
Tried binding command to SelectedItemChanged event with a parameter of treeviewitem name - didn`t work.
Tried it with a simple event - gets cumbersome and breaks MVVM pattern. There must be an elegant way to solve this since what I am trying to do is really widespread in different apps.
<TreeView Grid.Row="1" Background="AliceBlue" ItemsSource="{Binding TopPanelNodes}" Name="TopTreeView" SourceUpdated="TopTreeView_SourceUpdated" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Nodes}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" IsEnabled="False">
<TextBlock Text="{Binding Name}" >
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding UpdateInspectorCommand }" CommandParameter=??? />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
I would like to pass treeviewitem name as command parameter instead of ??? in my code so selected treeview item will be identified by name so I can get respective info and bind it to listview.
Solved it using RelativeSource.
CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeView}}}"
Works fine.

Why is my CommandParameter null when I have the Path as SelectedItem?

So I am trying to pass the SelectedItem as a parameter so that I can make use of the data that is bound to it.
Essentially I want to open a MessageBox and display the Name property of the User that is bound that item.
This is my xaml
<ItemsControl ItemsSource="{Binding CardViewModel.Users}"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultEffectDataTemplate="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserCard>
<controls:UserCard.ContextMenu>
<!-- Bind the DataContext of the CM to the DataContext that's bound to the RootObject-->
<ContextMenu DataContext="{Binding DataContext, Source={local:RootObject}}">
<MenuItem Header="Edit"
Command="{Binding CardViewModel.EditUser}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}},
Path=PlacementTarget.SelectedItem}"/>
</ContextMenu>
</controls:UserCard.ContextMenu>
</controls:UserCard>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The command works fine everything is bound just fine besides that when I click the MenuItem and it fires the command, I put a breakpoint where the action is and it shows the parameter as null I am suspecting that it's me who is binding it wrong.
public void DisplayEditUser(object user)
{
if (user != null)
{
MessageBox.Show("Not null");
}
}
The problem is that ContextMenu.PlacementTarget wasn't the ItemsControl but the UserCard, so binding source resolving will absolutely fail. To solve it, you need to bind ItemsControl.SelectedItem to one property of UserCard such as Tag as a relay.
<controls:UserCard Tag="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=ListBox}}">
CommandParameter="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType=ContextMenu}}"

Cannot bind command within a ListBox

My WPF uses the MVVM approach. I'm trying to bind 2 controls within my list control
<ListBox ItemsSource="{Binding ParentDuplicate}" SelectedItem="{Binding SelectedParent, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ContentControl Content="{Binding}" />
<Button Content="Delete me now"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType=Window}, Path=DeleteCommand}"
CommandParameter="{Binding FilePath}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem I'm having, is the DeleteCommand is not binding (the Ouput window informs me as well)
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Data.Binding', AncestorLevel='1''. BindingExpression:Path=DeleteCommand; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
If I move this button to outside the ListBox, then the binding works and the event fires so I know the issue must be with the ListBox (I'm guessing the problem is the ItemsSource prevents the binding to anything but the ItemsSource bound property (in this case, ParentDuplicate))
So, within the Button control, there are 2 properties being bound, DeleteCommand and FilePath
Both of these properties live within my single ViewModel. FilePath is a child of ParentDuplicate and this binds as desired. The issue is only with the DeleteCommand. What am I doing wrong?
Edit
When I use the Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType=Window}, Path=DeleteCommand} it is looking in my MainWindow code behind, not at the ViewModel. How do I make it use the ViewModel?
I tried
Command="{Binding RelativeSource={RelativeSource AncestorType=xmlnsViewModel:MainWindowViewModel}, Path=DeleteCommand}"
but the above also results in the same binding errors
The ancestor search finds the control, not the DataContext, so you'll need to tell your binding where to find the DeleteCommand property. If your ViewModel is the DataContext of the MainWindow then you can just use:
<Button Content="Delete me now"
Command="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorLevel=1, AncestorType=Window},
Path=DataContext.DeleteCommand}"
CommandParameter="{Binding FilePath}" />

LostFocus event firing upon getting Focus

I have an itemscontrol that is bound to a collection of objects.
in the data template, i bind an action that is to be used by all of the controls created if their focus is lost.
In the control, there is a textbox so if there are 1 items in the item controls ItemSource, there will be 2 textboxes.
Now, if Textbox 1 has focus and I click somewhere besides Textbox 2, the action is executed once (because the focus was lost). But if I click in Textbox 2, the action is executed twice. Why?
<ItemsControl Grid.Row="1" Margin="0,5,0,5" ItemsSource="{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=Collection}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type ct:CollectionItem}">
<cc:TestControl
ValueChangedAction="{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=ValueChangedAction}"
VerticalAlignment="Center" HorizontalAlignment="Center" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is the Control.
<ewt:DecimalUpDown Tag="{Binding Uid}" Grid.Row="0" Grid.Column="3"
HorizontalAlignment="Stretch" TextAlignment="Left" Margin="10,5,10,5"
FormatString="C2" ShowButtonSpinner="False" VerticalAlignment="Center"
Value="{Binding Value}" LostFocus="DecimalUpDown_LostFocus" />
The bound action is executed in this event handler.
If your action is supposed to update the source of the binding (e.g. the Window, it looks like from your example), you might want to try adding OneWayToSource to your Binding so that binding only occurs one way—from the textbox to whatever it's bound to.

Categories