I know I am the umpteenth with the same problem and I checked a lot of answers on this site, but none of them solved my issue.
I started with a combobox within a datagrid and it works as expected.
<ComboBox ItemsSource="{Binding DataContext.ItemList,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=DataGrid}}"
DisplayMemberPath="ItemName"
SelectedValuePath="ItemID"
SelectedValue="{Binding ItemID, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cal:ActionMessage MethodName="AddRowCheck">
<cal:Parameter Value="{Binding ElementName=OrderList, Path=CurrentItem}"/>
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
The only problem I have with this one is that when I add Items to the ItemList, they show up add the bottom of the list and not in alphabetical order anymore.
To solve this, I updated my code like this
<Window.Resources>
<CollectionViewSource x:Key="sortedItemList"
Source="{Binding ItemList}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ItemName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
.......
<ComboBox ItemsSource="{Binding Source={StaticResource sortedItemList}}"
DisplayMemberPath="ItemName"
SelectedValuePath="ItemID"
SelectedValue="{Binding ItemID, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cal:ActionMessage MethodName="AddRowCheck">
<cal:Parameter Value="{Binding ElementName=OrderList, Path=CurrentItem}"/>
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
and now the Items are in alphabetical order, but the selected Item is no longer shown.
What am I a doing wrong?
I tried to override my equal function on my Item object so it checks the ID to see if two items are equal, but that did not solve the problem.
I found the answer myself. The error was in the event that was triggered after an item is selected.
In the AddRowCheck method, I add a new empty row to the datagrid with a ComboBox pointing to the same CollectionViewSource.
I do not understand the logic behind it completely, but after checking this site with this new information I found that I have to set
IsSynchronizedWithCurrentItem="False"
and it worked.
Related
In the below piece of code, nested menu's get generated from an observable collection called CollectionOfAuthors. I've placed two commands, One at the 'ToplevelMenuItem' and another for submenu item (through textblock.InputBindings).
Though the command for submenuitem is working, I am unable to hit the command for the TopLevelMenuItem:
Need to understand what extra i need to do ?
<MenuItem x:Name="TopLevelMenuItem" Header="Authors" ItemsSource="{Binding CollectionOfAuthors}" Command="{Binding DataContext.RefreshAuthorsList, RelativeSource={RelativeSource AncestorType=Menu}}" >
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<TextBlock Text="{Binding AuthorName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding BookName}" >
<TextBlock.InputBindings>
<MouseBinding Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
Try with
<MenuItem x:Name="TopLevelMenuItem" Header="Authors" ItemsSource="{Binding CollectionOfAuthors}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SubmenuOpened">
<i:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
If you want to prevent from firing command from submenues you will need to pass {Binding .} to command, and if param is not type of your ViewModel, then don't fire your action.
Also, you need to add reference to System.Windows.Interactivity and
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" in your xaml header
My problem is that I can't seem to time the selection changed event from the ComboBox edit, to the TextBlock update. Everything displays property but when I'm getting the Grade in the SelectionChanged event in the code behind, it's not updated with the actual value. To get the actual value I have to change the selection on the ComboBox then click off the line of the datagrid I'm editing. Then when I choose a new grade it is updated to show what the last grade was selected. So even though it seems it should all be connected, the row does not actually update until I click off it, as opposed to when I make a different ComboBox selection. So is there any way to change the way a datagrid updates the data to keep in sync with the ComboBox? Any help is appreciated. Thanks.
<DataGridTemplateColumn Header="Grade" CellStyle="{StaticResource StarNumberCellStyle}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Grade}" Margin="3,1,3,1">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.Grades, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
SelectedItem="{Binding Grade,Mode=TwoWay, NotifyOnTargetUpdated=True}" Tag="{Binding}" ComboBox.IsTextSearchEnabled = "True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding DataContext.GradeSelectCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter ="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
See my answer about DataGridTemplateColumn caveats here: DataGrid and Observable Collection in WPF
In short, this should work for you (UpdateSourceTrigger=PropertyChanged):
<ComboBox ...
SelectedItem="{Binding Grade, UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay, NotifyOnTargetUpdated=True}"
... >
I want to execute a command when the user selects a row in a DataGrid.
I see it is possible to wrap cell contents in buttons (although I don't want the button style) - but I don't want to do it at the cell-level.
I also see it's possible to use behaviours to link a command to an event. But preferably I should not have to resort to behaviors for such a common task.
Is it possible to do this via plain old command databinding?
So: 1) user clicks DataGrid row 2) command on view model is fired.
You should use "Interactivity" assembly and SelectionChanged event.
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding People}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Where "i" is namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Also you can write binding to SelectedItem property of the DataGrid and in the set accessor you can invoke your command, but the first solution that i presented you above is better.
If you want to invoke command from main view model and pass SelectedItem from DataGrid you can use CommadParameter:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}"
CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
When items has own command you can use following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=SelectedItem.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Or if elements has own view model that is assigned to it's DataContext, you can use following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=SelectedItem.DataContext.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Be advised that #kmatyaszek's answer is outdated in .NET 5.0 and above, we should use Microsoft.Xaml.Behaviors instead of Microsoft.Expression.Interactions.
So the i in the namespace should be:
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
See here for details.
Seems like this would be a common problem...
I'm using MVVM Light
I have a listbox defined in my xaml
<ListBox ItemsSource="{Binding Events}" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand Command="{Binding EventPageCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="0,0,0,22">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Preview}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This works all fine and dandy, except the user is able to click below this list and the event is still fired.
I can modify it to use SelectionChanged such as:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding EventPageCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But then if I use the back button, I cannot select the same item twice since the selection hasn't actually changed. I could probably go into my codebehind and set the selected index = -1 on page load, but this seems like quite a hack, and I use list boxes in quite a few places and I would hate to have to remember to do this hack everywhere.
Any ideas?
edit
I added background color to my listbox and my listbox items and while my listbox items only take up what space the text occupies, the listbox takes up the entire screen, extending below the last listbox item. Perhaps one way would be to shore up this extra space.
update
what seems to work best so far is to set the VerticalAlignmnet="Top" on the listbox (this shores up the extra space on the bottom, not sure why, but then scrolling ends at the bottom of the list and not the bottom of the screen). I then disabled the scrolling on the listbox itself ScrollViewer.VerticalScrollBarVisibility="Disabled" and wrapped the whole thing in a <ScrollViewer>
Currently you are looking for the Tap event on the whole of the list box, not a individual item.
This is how I typically I use interaction triggers:
<DataTemplate DataType="{x:Type ViewModel:DataItem}" x:Key="ItemTemplate">
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding Tap}"/>
</i:EventTrigger>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction
Command="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}},
Path=DataContext.KeyUpCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox Text="{Binding Name}"/>
</ContentControl>
</DataTemplate>
In this example the Tap event binds to a command on the DataContext (a data item) of an ItemsControl, whereas the KeyUp event binds to a command on the parent DataContext of the ListBox.
If all you want is to get an event when the user taps an item, why don't you use the trigger code you have on the Border element of the template?
You probably will have to use the Source property to bind the command to the ViewModel, but that can also be easely achieved (will just depend on how you are setting the Page DataContext right now)
I am trying to run a command from a GridViewColumn, and the RadGridView is bound to my ViewModel. The code below runs the command, but when I select a value from the ComboBox, it saves the row immediately without allowing me to make changes to other cells first. This behavior happens whether or not I have the command bound to the RADComboBox. If I take the command away, still happens.
XAML:
<telerik:RadGridView x:Name="GV1"
ItemsSource="{Binding Path=Material}"
AutoGenerateColumns="False"
IsReadOnly="{Binding IsGridReadOnly}"
ShowGroupPanel="False"
VerticalAlignment="Top"
HorizontalAlignment="Center"
RowDetailsVisibilityMode="VisibleWhenSelected">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnded">
<cmd:EventToCommand Command="{Binding RowEditEndedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="CancelRowEdit">
<cmd:EventToCommand Command="{Binding CancelEditCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<telerik:RadGridView.Columns>
<telerik:GridViewColumn Header="Material Code">
<telerik:GridViewColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox ItemsSource="{Binding DataSource.AllMaterials, Source={StaticResource DCP}}"
DisplayMemberPath="Code"
SelectedValuePath="Code"
SelectedValue="{Binding Path=MaterialCode, Mode=TwoWay}"
IsEnabled="{Binding Path=IsMaterialEditable, Mode=TwoWay}"
Command="{Binding DataSource.MaterialCodeChangedCommand, Source={StaticResource DCP}}"/>
</DataTemplate>
</telerik:GridViewColumn.CellEditTemplate>
<telerik:GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MaterialCode}" />
</DataTemplate>
</telerik:GridViewColumn.CellTemplate>
</telerik:GridViewColumn>
<!--7 Other Columns here-->
</telerik:RadGridView.Columns>
</telerik:RadGridView>
Code Behind
public RelayCommand MaterialCodeChangedCommand { get; private set; }
//IN Constructor
this.MaterialTypeChangedCommand = new RelayCommand(MaterialTypeChange);
private void MaterialCodeChange()
{
//Command code here
}
When I take the ComboBox out of the CellEditTemplate and try to use the GridViewComboBox, I can't figure out how to get the binding to work, but it doesn't fire off the RowEditEnded just by selecting the drop down so that part work correctly with this code:
<telerik:GridViewComboBoxColumn Header="Material Type"
ItemsSource="{Binding DataSource.AllTypeCodes, Source={StaticResource DCP}}"
SelectedValueMemberPath="Code"
DataMemberBinding="{Binding Path=MaterialType, Mode=TwoWay}"
DisplayMemberPath="Display">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding DataSource.MaterialTypeChangedCommand, Source={StaticResource DCP}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</telerik:GridViewComboBoxColumn>
If this is unclear let me know, it's late and I may not be clear. Thanks for any help you can offer. I should note that the RowEditEnded acts like this on other drop downs in my application as well when set up in the CellEditTemplate. Converting to the to the GridViewComboBoxCOlumn resolves that but then the binding issue...