I have a ListView which populates the view with ListViewItems containing an image and text(file browser). How can I fire a Command when the user presses the 'Enter' key on a selected item while respecting the MVVM design pattern? I've searched and found a few solutions, but none of them seem to work for me.
<ListView ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.ScrollUnit="Item"
Background="#fdfaf4"
Name="filesView"
ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- The image and item name -->
<Grid Width="{Binding ActualWidth, ElementName=filesView, Converter={x:Static converter:GridWidthToListViewWidthConverter.Instance}}"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.07*" MinWidth="25" MaxWidth="40" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Drive, file or folder -->
<Image Grid.Column="0"
Margin="0,0,5,0"
Name="itemType"
Source="{Binding Type,
Converter={x:Static converter:HeaderToImageConverter.Instance}}" />
<!-- The text is binded to the image size, so they'll expand/shrink together -->
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="{Binding ActualHeight,
ElementName=itemType, Converter={x:Static converter:ImageSizeToFontSizeConverter.Instance}}"
Text="{Binding Name}" />
<!-- The command to enter a drive/folder is called from here -->
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EnterCommand, Mode=TwoWay}" />
<KeyBinding Key="Enter" Command="{Binding EnterCommand, Mode=TwoWay}" />
</Grid.InputBindings>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The MouseBinding works just fine. I've tried putting the KeyBinding in the ListView instead of the grid and getting the focused item with the SelectedItem property but still nothing.
Implement the PreviewKeyDown event for the root Grid in the ItemTemplate or the ListViewItem container in the code-behind of the view and simply execute the command from there, e.g.:
private void ListViewItem_PreviewKeyDown(object sender, KeyEventArgs e)
{
var viewModel = DataContext as YourViewModel;
viewModel.YourCommand.Execute(null);
}
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewKeyDown" Handler="ListViewItem_PreviewKeyDown" />
</Style>
</ListView.ItemContainerStyle>
Or implement a behaviour that hooks up the event handler and does the same: https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF.
Neither approach breaks the MVVM pattern since you are invoking the exact same view model command from the exact same view that the XAML markup is a part of.
MVVM is not about eliminating code from the views, it is about separation of concerns. If you invoke the command using a KeyBinding or an event handler doesn't matter.
Try Gesture="Enter";
<Grid.InputBindings>
<KeyBinding Gesture="Enter" Command="{Binding EnterCommand}" />
</Grid.InputBindings>
Related
For Command "CommandZoomIn", CanExecute and Execute do not occurs for controls defined into the ListBox ItemTemplate. GraphLcView method "CanExecute" and "Execute" are both called when GraphLcView UserControl is defined directly as child of AnalysisView, but both methods are never called when they are added as Item DataTemplate of a ListBox ItemTemplate.
The button with the command is defined in my top level window into a Ribbon.
Simplified hierarchy:
(Working) Top Level window -> AnalysisView -> GraphLcView
(Not working) Top Level window -> AnalysisView -> ListBox + ItemTemplate -> GraphLcView
The CommandBinding is defined into GraphLcView child control (UserControl.CommandBinding)
There is no MVVM implied into the CommandBindind
Update: I made a working sample to demo the problem but I had a different behavior than explained here. But the fully working sample should probably show something similar to what I have here. Because the bahvior is different, I asked another question at StackOverflow. Code is available here at GitHub
User Control 'GraphLcView' partial code:
<UserControl x:Class="GraphCtrl.GraphView.GraphLcView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
<Grid.CommandBindings>
<CommandBinding Command="{x:Static graphCtrlCommand:CtrlAnalysisCommand.CommandZoomToFitsAll}" CanExecute="CanZoomToFitsAll" Executed="ZoomToFitsAll"/>
<CommandBinding Command="{x:Static graphCtrlCommand:CtrlAnalysisCommand.CommandZoomIn}" CanExecute="CanZoomIn" Executed="ZoomIn"/>
<CommandBinding Command="{x:Static graphCtrlCommand:CtrlAnalysisCommand.CommandZoomOut}" CanExecute="CanZoomOut" Executed="ZoomOut"/>
UserControl AnalysisView partial code (where previous GraphLcView UserControl is used):
<!-- ********************************-->
<!-- ********************************-->
<!-- CommmandBinding works fine here -->
<!-- ********************************-->
<!-- ********************************-->
<graphView1:GraphLcView Grid.Row="1" x:Name="GraphView" Graph="{Binding Graph}"
Visibility="{Binding IsMain, Converter={StaticResource BooleanToVisibilityConverter1}}"
TrackedSignal="{Binding DataContext.LastTrackedSignal, Mode=TwoWay, ElementName=MyControl}"
SourceTrackedSignal ="{Binding Model.EventTrackingSourceGraphToLegend, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=MyControl}"
IsMouseInteractive="{Binding IsMouseInteractive}"
UseFastTooltip="{Binding UseFastTooltip}"
ActiveObjectChanged="OnChildActiveObjectChanged"
>
</graphView1:GraphLcView>
<Grid Name="GridDetails" Grid.Row="1" >
<ListBox Name="ListBoxDetails" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Graph.AdditionalViews}"
Visibility="{Binding IsDetails, Converter={StaticResource BooleanToVisibilityConverter1}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Name="DetailsWrapPanel"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Margin="0,1,0,1"
Width="{Binding DataContext.DetailsWorkspaceDimensionX, ElementName=MyControl, Mode=OneWay}"
Height="{Binding DataContext.DetailsWorkspaceDimensionY, ElementName=MyControl, Mode=OneWay}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Name}"></TextBlock>
<!-- ********************************-->
<!-- ********************************-->
<!-- Binding does not work fine here -->
<!-- ********************************-->
<!-- ********************************-->
<!--ActiveObjectChanged="GraphLcViewDetailOnActiveObjectChanged"-->
<!--SourceTrackedSignal="{Binding DataContext.EventTypeSourceForSignalTrackingToGraph, Mode=TwoWay, ElementName=MyControl}"-->
<graphView1:GraphLcView Grid.Row="1"
AdditionalView="{Binding Path=., Mode=OneWay}"
Graph="{Binding Graph, ElementName=GraphView}"
TrackedSignal="{Binding DataContext.LastTrackedSignal, Mode=TwoWay, ElementName=MyControl}"
SourceTrackedSignal ="{Binding Model.EventTrackingSourceGraphToLegend, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=MyControl}"
IsMouseInteractive="{Binding IsMouseInteractive}"
UseFastTooltip="{Binding UseFastTooltip}"
ActiveObjectChanged="OnChildActiveObjectChanged"
>
</graphView1:GraphLcView>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I'm sorry. After investigation, I realised that the problem came from the LightningChart control which does not keep the focus. I added 2 handlers for "GotFocus" and "LostFocus". Then I realised that everything went fine for the control in the first tab, which is not part of a ListBox itemTemplate. But all others, that are in the second tab, into a ListBox itemTemplate, lost the focus as soon as they got it for no particular reason (at least none that I can understand).
I sent the problem to Arction, the company under LightningChart and they told me they will try to fix it soon.
Trying to get some keybindings onto my ListBoxItems in a ListBox in WPF. I am using MVVM, and binding the ItemSource of the ListBox to a list of ViewModels. This ViewModel has a string and a boolean for 'Selected'. I wish to display Selected as a property to a CheckBox.
I am trying to make it so that if I navigate the list items with the up and down arrows on the keyboard, and then press enter/space/whatever, I can toggle the Checkbox. However, I have to press tab first, to get focus to the StackPanel which contains the checkbox.
<DataTemplate x:Key="MyTemplate" DataType="{x:Type ViewModel}">
<Border Width="2" BorderBrush="Blue">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding EnterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<CheckBox VerticalAlignment="Center"
Content="{Binding Name}"
IsChecked="{Binding Selected}"
Margin="3" />
</Border>
</DataTemplate>
=======================
<Popup x:Name="FilterPopup" Grid.Column="1"
IsOpen="{Binding IsChecked, ElementName=FilterButton}"
StaysOpen="False"
PlacementTarget="{Binding ElementName=FilterButton}"
Placement="Top">
<ListBox ItemsSource="{Binding ViewModels}"
ItemTemplate="{StaticResource MyTemplate}" />
</Popup>
Have I missed something obvious???
The triggers above are fired inside the data template, not inside item container. So if the last one is focused there is no effect.
To avoid this, the triggers should be specified on item container level:
<ListBox.ItemContainerStyle>
<Style>
<Setter Property="l:Attach.InputBindings">
<Setter.Value>
<InputBindingCollection>
<KeyBinding Command="{Binding EnterCommand}" Key="Enter" />
<KeyBinding Command="{Binding EnterCommand}" Key="Space" />
</InputBindingCollection>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
I've taken a way to set input bindings from style from this question.
I have a TabControl bound to an ObservableCollection property of view models, TabViewModelsCollection.
When another property gets set from within the view models, DeviceState, I'd like to raise a property changed event and tell my TabControls ItemSource to refresh.
The problem is my ItemSource is an ObservableCollection of ViewModels and when I call RaisePropertyChanged("TabViewModelsCollection"); nothing gets updated.
Furthermore my tab views contain multiple user controls and bindings.
The scenario that is supposed to play out is: A device is located on the network, data is collected, and the tabs of device information should then be updated.
Currently my TabControl only updates when I select a different device then select the device whos information I want to see. Think left panel list of devices, right panel with tabs of device info.
Let me know what part of the code you guys might want to see, my codebase is quite large so it would be hard to post it.
Here is where the TabControl is defined in my view:
<!-- Devist List Controls -->
<Grid DockPanel.Dock="Left" Margin="5,5">
<local:DeviceListView DataContext="{Binding DeviceListViewModel}" Grid.Row="0"/>
</Grid>
<GroupBox Header="Device Information" DockPanel.Dock="Right" Margin="0,0,5,5">
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection}" SelectedItem="{Binding DeviceListViewModel.SelectedDevice.SelectedTabItemVm}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type vms:HomeViewModel}">
<local:HomeTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:ConfigurationViewModel}">
<Grid>
<local:ConfigurationFileView Visibility="{Binding Configuration, TargetNullValue=Collapsed, FallbackValue=Visible}"/>
<local:ErrorTab Visibility="{Binding Path= Configuration, TargetNullValue=Visible, FallbackValue=Hidden}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:ExpansionModulesViewModelFactory}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<DockPanel >
<local:ExpansionModulesList Title="Discovered/Enumerated"
DataContext="{Binding DiscoveredModules}"
/>
<GridSplitter Width="5"/>
<local:ExpansionModulesList Title="User Action Required"
DataContext="{Binding FaultyModules}"
/>
</DockPanel>
</StackPanel>
<DockPanel Grid.Row="1">
<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft" Margin="5" IsEnabled="{Binding IsCommandEnabled}">
<Button Content="Cancel" HorizontalAlignment="Right"
Command="{Binding CancelExpansionCommand }"
ToolTip="Revert all local modifications by refreshing data from the controller." />
<Separator Width="10"/>
<Button Content="Apply" HorizontalAlignment="Center"
Command="{Binding ApplyExpansionCommand }"
ToolTip="Apply all changes to the controller." />
<Separator/>
</StackPanel>
</DockPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:LogViewModel}">
<local:LogView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:SignalStrengthViewModel}">
<local:SignalStrengthView />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.ItemContainerStyle>
EDIT: I want to be able to call raised property changed on this and have it refresh all of my tab views..
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection}" SelectedItem="{Binding DeviceListViewModel.SelectedDevice.SelectedTabItemVm}" >
RaisePropertyChanged("TabViewModelsCollection");
Hi please try the next solution:
VM code redaction
private State _deviceState;
private ObservableCollection<object> _tabViewModelsCollection;
public State DeviceState
{
get { return _deviceState; }
set
{
_deviceState = value;
RaisePropertyChanged("DeviceState");
UpdateTabViewModelsCollection();
}
}
public ObservableCollection<object> TabViewModelsCollection
{
get
{
return _tabViewModelsCollection ??
(_tabViewModelsCollection = new ObservableCollection<object>(GetDeviceData()));
}
}
private void UpdateTabViewModelsCollection()
{
_tabViewModelsCollection = null;
RaisePropertyChanged("TabViewModelsCollection");
}
private List<object> GetDeviceData()
{
//implement here the data collection process
throw new NotImplementedException();
}
Xaml redaction (define the UpdateSourceTrigger)
ItemsSource="{Binding DeviceListViewModel.SelectedDevice.TabViewModelsCollection, UpdateSourceTrigger=PropertyChanged}"
Let me know if it was helpful.
Regards.
I made a Usercontrol with a Combobox with itemTemplate. I set a an event trigger for click on Item. but its not work completely. it dosent accept the click. around the template or empty place before my text.
this is my code
<Combobox>
<Combobox.ItemTemplate>
<DataTemplate>
<Grid Height="25" FlowDirection="RightToLeft">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25" />
<ColumnDefinition MinWidth="100" />
<ColumnDefinition Width="25" />
</Grid.ColumnDefinitions>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<command:EventToCommand Command="{Binding Command}"
CommandParameter="{Binding CommandParameter}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Image Height="20" Width="25" Grid.Column="0" VerticalAlignment="Center"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding Title}" Grid.Column="1" VerticalAlignment="Center" />
<TextBlock Grid.Column="2" />
</Grid>
</DataTemplate>
</Combobox.ItemTemplate>
</Combobox>
it is a usercontrol that binds to a list of object contains Command and commandparameter, on click on each item one command should be raised.
Visual elements need to be assigned a brush in order for hit testing to take place.
(I did say IsHitTestVisibile so you wouldn't confuse the two).
You can do the following above your ItemTemplate in the Container that hosts it like so :
<ComboBox>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}"> <!-- Or based on any other ComboboxItem style you have-->
<Setter Property="Background" Value="Transparent" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
FYI : IsHitTestVisibile is a flag stating that even if a Hit test did pass you can choose to disregard it.
What's wrong with SelectionChanged event of ListBox?
You could bind to that.
DataTemplate is for the data not for UI events. You use data-templates to tell WPF how you want to display data. At most you could have DataTriggers (which is again belong to data).
If you want to trap the click event on items, use ItemContainerStyle. The ItemContainerStyle is for styling the container of dataitem, which is ListBoxItem in this case.
Something of this sort might help:
<Style TargetType="ListBoxItem">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
</EventTrigger>
</Style.Triggers>
</Style>
I am trying to disable the right click functionality of my items inside a list view. I have tried handling the RightTapped event and setting the e.handled value to true. This has covered most of the list view's area however if I right click on the borders of the list view item, this selection style is still applied. I want the user to still be able to use the left click and apply the list selected styling. It is just the right click I want to disable.
Here is what I have tried so far:
<ListView
ItemsSource="{Binding MyItems}"
ItemClick="OnItemClicked"
IsItemClickEnabled="True"
SelectionMode="Multiple"
CanDragItems="True"
DragItemsStarting="OnDragItemsStarting"
RightTapped="OnRightTapped"
IsRightTapEnabled="False">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Margin" Value="-1,3,-1,0" />
<Setter Property="IsRightTapEnabled" Value="False"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" RightTapped="OnRightTapped" HorizontalAlignment="Left" Height="100" Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding ImagePath}" Width="55" Height="55" Margin="0,0,0,1"/>
<Image Margin="20,0,0,22" Grid.Column="0" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="20" Height="20" Source="smallImage.png"/>
<StackPanel VerticalAlignment="Center" Margin="30,0,0,0" Grid.Column="1">
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" Foreground="{Binding IsOnline, Converter={StaticResource BooleanToColorConverter}}" Style="{StaticResource BaseTextBlockStyle}" FontSize="20" Height="60"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is the code for the RightTapped handler:
private void OnRightTapped(object sender, RightTappedRoutedEventArgs e)
{
e.Handled = true;
}
Edit: When I click on the borders of an Item in the ListView the OnRightTapped event is not being fired. What could be swallowing this event?
By playing with the background colors of the items inside my ListView.ItemTemplate, I found that my TextBlock was not covering the whole area of the row. By setting the Column width to the exact size of the ListView and setting the RightTapped event handler on this Grid I was able to cover all areas of right clicking. I still do not know why the RightTapped handler was not being fired for the ListView itself but this was a sufficient solution for what I needed.