I need an ApplicationBar which can be bound to both commands and tap events (tap events are needed for UI related stuff and I can't handle them in my Model).
I'm trying with BindableApplicationBar from NuGet and the command binding works fine but there's no way to get the tap events.
Please note that I'm changing the DataContext to make the binding work but the Tap event has to be managed in the code-behind (and I don't know if this may be the main cause of my problem!)
Here's the bar's xaml:
<bindableApplicationBar:Bindable.ApplicationBar>
<bindableApplicationBar:BindableApplicationBar>
<bindableApplicationBar:BindableApplicationBarButton
Text="{Binding MainSearchText}"
IconUri="Assets/AppBar/feature.search.png"
Command="{Binding NavigateCommand}"
Name="SearchBarButton" />
<bindableApplicationBar:BindableApplicationBarButton
Text="{Binding Path=LocalizedResources.AppBarSettingsText, Source={StaticResource LocalizedStrings}}"
IconUri="Assets/AppBar/feature.settings.png"
Tap="SettingsButton_OnTap" />
<bindableApplicationBar:BindableApplicationBar.MenuItems>
<bindableApplicationBar:BindableApplicationBarMenuItem
Text="{Binding Path=LocalizedResources.AppBarAboutText, Source={StaticResource LocalizedStrings}}"
Tap="Info_OnTap" />
</bindableApplicationBar:BindableApplicationBar.MenuItems>
</bindableApplicationBar:BindableApplicationBar>
</bindableApplicationBar:Bindable.ApplicationBar>
and here's one of the handlers that I have in my .xaml.cs file:
private void Info_OnTap(object sender, GestureEventArgs e)
{
_about.Show();
Debug.WriteLine("INFO ON_TAP");
}
No "INFO ON_TAP" line is written when I click on the MenuItem.
What's wrong with it?
This is not how you would use BindableApplicationBar. BindableApplicationBarButton inherits from FrameworkElement (and thus has Tap event) to support DataContext and Bindings; the Tap event is not fired because no BindableApplicationBarButton is ever tapped, no BindableApplicationBarButton is ever onscreen - this is only a wrapper that creates an ApplicationBarIconButton, but doesn't pass the Tap event handler to it (ApplicationBarIconButton, by the way, has only one event - Click). The same goes for BindableApplicationBarMenuItem. BindableApplicationBar works best with Commands.
See more in comments in source: BindableApplicationBar # codeplex
Here are (some of) your options:
use Command property of BindableApplicationBarButton to react to buttons clicks
after defining BindableApplicationBar in xaml, hookup to Click events of ApplicationBarIconButtons it created in code behind
use BindableApplicationBar code (open source) and alter it for best experience in your scenerio
You can try Cimbalino Toolkit AppBar ... much more powerfull and easier to use!
For example:
<i:Interaction.Behaviors>
<cimbalinoBehaviors:MultiApplicationBarBehavior
SelectedIndex="{Binding SelectedIndex, ElementName=MainInfo, Converter={StaticResource HomeMenuConverter}}" >
<cimbalinoBehaviors:ApplicationBar Opacity="0.5"
IsMenuEnabled="{Binding IsLoading, Converter={StaticResource NegativeBooleanConverter}}">
<cimbalinoBehaviors:ApplicationBarIconButton
IsVisible="{Binding IsAuthenticated}"
IsEnabled="{Binding IsLoading, Converter={StaticResource NegativeBooleanConverter}}"
Command="{Binding GetFavorites, Mode=OneTime}"
IconUri="/Assets/appbar.sync.rest.png" Text="{Binding Labels.Translation.Refresh, Source={StaticResource LabelsManager}}" />
<cimbalinoBehaviors:ApplicationBarIconButton
IsVisible="{Binding SelectionMode, Converter={StaticResource NegativeBooleanConverter}}"
IsEnabled="{Binding IsLoading, Converter={StaticResource NegativeBooleanConverter}}"
Command="{Binding SetSelectionMode, Mode=OneTime}"
IconUri="/Assets/ApplicationBar.Select.png" Text="{Binding Labels.Translation.Select, Source={StaticResource LabelsManager}}" />
<cimbalinoBehaviors:ApplicationBarIconButton
IsVisible="{Binding SelectionMode}"
IsEnabled="{Binding IsLoading, Converter={StaticResource NegativeBooleanConverter}}"
Command="{Binding DeleteFavorites, Mode=OneTime}"
IconUri="/Assets/ApplicationBar.Delete.png" Text="{Binding Labels.Translation.Delete, Source={StaticResource LabelsManager}}" />
<cimbalinoBehaviors:ApplicationBarIconButton
IsVisible="{Binding SelectionMode}"
IsEnabled="{Binding IsLoading, Converter={StaticResource NegativeBooleanConverter}}"
Command="{Binding SetSelectionMode, Mode=OneTime}"
IconUri="/Assets/ApplicationBar.Cancel.png" Text="{Binding Labels.Translation.Cancel, Source={StaticResource LabelsManager}}" />
</cimbalinoBehaviors:ApplicationBar>
</cimbalinoBehaviors:MultiApplicationBarBehavior>
</i:Interaction.Behaviors>
https://github.com/Cimbalino/Cimbalino-Phone-Toolkit
Related
There is a WPF MVVM app. On the main view I have a list of elements, which are defined with ListView.ItemTemplate, in that I want to have a context menu with Delete action.
The Command for that is separated from the view and is kept in ViewModel DreamListingViewModel.
The problem is that on clicking on Delete I can't get it to execute the command on ViewModelk as context there is that of the item, not the items container.
I can make it work somehow by moving the context menu definition outside of the list view elements, but then when I open the context menu, it flickers, as if it's being called "20" times (which what I think does happen, as many times as I have elements in collection), anyways, I need a clean solution for that and I am very bad with XAML.
Here is how my View looks:
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type viewmodels:DreamListingViewModel}}}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
It's the main window and initialized in a generic host in App.cs:
public partial class App : Application
{
private readonly IHost _host;
public App()
{
...
_host = Host.CreateDefaultBuilder().ConfigureServices(services =>
{
...
services.AddTransient<DreamListingViewModel>();
services.AddSingleton((s) => new DreamListingView()
{
DataContext = s.GetRequiredService<DreamListingViewModel>()
});
...
}).Build();
The Command and CommandParameter values are what I've been experimenting with, but it doesn't work
Here is how my ViewModel looks:
internal class DreamListingViewModel : ViewModelBase
{
public ICommand DeleteSelectedDream{ get; }
...
Finally, when the command is fired, I need to pass the current element on which the menu has been shown.
So, here is what I want:
User clicks on a list item with mouse right button - OK
Sees a menu with Delete entry - OK
On Delete click, Command DeleteSelectedDream is fired with current dream (item in the list) as a parameter - ERR
Your example is somewhat lacking necessary information, but I'll try to help.
First you need to verify that you are actually bound to your view model. Are you using Prism or just standard WPF ? In the constructor of your code-behind of your view, set up the DataContext to an instance of your VM.
InitializeComponent();
this.DataContext = new DreamListingViewModel();
Now, you bind to a relative source via Mode 'FindAncestor' and the AncestorType is set to the type of a view model. That usually won't work, as the view model is not naturally a part of the visual tree of your WPF view. Maybe your ItemTemplate somehow wires it up. In a large WPF app of mine I use Telerik UI for WPF and a similar approach to you, however, I set up the DataContext of the Context menu to a RelativeSource set to Self combined with Path set to PlacementTarget.DataContext.
You do not have to use all the XAML in my example, just observe how I do it. Exchange 'RadContextMenu' with 'ContextMenu', Ignore the Norwegian words - here and only use what you need :
<telerik:RadContextMenu x:Key="CanceledOperationsViewContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="{Binding PatientName}" IsEnabled="False" Style="{StaticResource ContextMenuHeading}" />
<MenuItem Header="Gå til aktuell SomeAcme-liste" IsEnabled="{Binding IsValid}" Command="{Binding NavigateToListCommand}" />
<MenuItem Header="Åpne protokoll..." Command="{Binding CommonFirstProtocolCommand, Mode=OneWay}" CommandParameter="{Binding}" />
<MenuItem Header="Åpne Opr.spl.rapport...." Command="{Binding CommonFirstNurseReportCommand, Mode=OneWay}" CommandParameter="{Binding}" />
</telerik:RadContextMenu>
In your example it will be :
<ContextMenu x:Key="SomeContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="Delete" />
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListViewItem}}}"
/>
</telerik:RadContextMenu>
Now I here consider you are using the class ListViewItem
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.listviewitem?view=netframework-4.8
It might be that you need to specify DataContext.DeleteSelectedDream here to be sure you bind up to the DataContext where your implementation of ICommand is.
Accidentally found this answer, that's basically what I needed, just added to it a CommandParameter to send the item and it works like magic!
<ListView Name="lvDreams" ItemsSource="{Binding Dreams}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem
Header="Delete"
Command="{Binding DataContext.DeleteSelectedDream, Source={x:Reference lvDreams}}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I find the following the simplest; perhaps it's because I do not understand WPF, but it's "simple" to remember, and it works with my MVVM pattern.
<ListBox ItemsSource="{Binding MyViewModelItemsCollection, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" >
<TextBlock Text="{Binding Path=Name, Converter={StaticResource FullPathToFileName}, Mode=OneWay}" Grid.Column="0">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem
Command="{Binding Path=DataContext.MyViewModelAction, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Header="{Binding Name, Converter={StaticResource resourceFormat}, ConverterParameter={x:Static res:Resources.CONTEXT_MENU_BLOCK_APPLICATION}}">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</grid>
</DataTemplate>
</ListBox.ItemTemplate
</ListBox>
The MyViewModelXXXXXXX named items are in the view model that is mapped to the data context of the control.
I'm using Caliburn Micro Message.Attach through XAML to try and bind Events to a View Model, but I cannot get the TreeViewItem.Expanded Event to fire. Other events like SetSelectedItem work fine.
I found another question on this on SO here but it was not helpful in my case as no context for the response was provided.
The only other information I can find is the following GitHub issue.
Internally Caliburn.Micro turns
<Button cm:Message.Attach="[Event Click] = [Action Test]" />
into
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cm:ActionMessage MethodName="Test" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
As you noted, EventTrigger doesn't support attached events. A quick look around brought up How to attached an MVVM EventToCommand to an Attached event which shows how to create a RoutedEventTrigger that you could plug into the full syntax.
Again, I tried this approach, but don't fully understand how to implement this. It fires the event in the custom class, but never gets passed on to my handler in the View Model.
Here is my XAML (without the GitHub suggestion):
<TreeView x:Name="FolderView"
cal:Message.Attach="[Event TreeViewItem.Expanded] = [Action Expanded($this)];
[Event SelectedItemChanged] = [Action SetSelectedItem($this.SelectedItem)]">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:LogicalDriveItem}"
ItemsSource="{Binding Directories}" >
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=DriveLetter}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:DirectoryItem}"
ItemsSource="{Binding Directories}">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Path}"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
And my view model code:
public void Expanded(object sender, RoutedEventArgs e)
{
// This won't fire
}
public void Expanded(object sender)
{
// Or this
}
public void SetSelectedItem(object sender)
{
// But this will
}
The link provided by mm8 resolved my issue.
The OP in that question is using the same RoutedEventTrigger helper class that I found on GitHub, but the additional context provided by their answer was helpful. Using the RoutedEventTrigger helper class, I updated my XAML to the following:
<i:Interaction.Triggers>
<!--in the routed event property you need to put the full name space and event name-->
<helpers:RoutedEventTrigger RoutedEvent="TreeViewItem.Expanded">
<cal:ActionMessage MethodName="Expanded">
<cal:Parameter Value="$eventArgs" />
</cal:ActionMessage>
</helpers:RoutedEventTrigger>
</i:Interaction.Triggers>
which now successfully fires my event in the ViewModel code.
Note that $this did not work for me because the data item in my case is a string. In my case, it's a File Explorer style Tree View. For context, here is the full XAML:
<TreeView x:Name="FolderView">
<i:Interaction.Triggers>
<!--in the routed event property you need to put the full name space and event name-->
<helpers:RoutedEventTrigger RoutedEvent="TreeViewItem.Expanded">
<cal:ActionMessage MethodName="Expanded">
<cal:Parameter Value="$eventArgs" />
</cal:ActionMessage>
</helpers:RoutedEventTrigger>
</i:Interaction.Triggers>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:LogicalDriveItem}"
ItemsSource="{Binding Directories}" >
<StackPanel Orientation="Horizontal">
<!--<Image MaxWidth="20" Source="Images/Image.png"/>-->
<TextBlock VerticalAlignment="Center" Text="{Binding Path=DriveLetter}"></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:DirectoryItem}"
ItemsSource="{Binding Directories}">
<TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
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>
I'm using MVVM-light approach for Pushpins on my Map.
I have bound the Tapped Event to a Command in my ViewModel.
However the event is not triggered.
All the other commands and properties bind perfectly.
I have also tried as an example to use a regular event, but it's also not firing.
My Command in my VM
private RelayCommand<Check> _showDetailCheckCommand;
public RelayCommand<Check> ShowDetailCheckCommand
{
get
{
Debug.WriteLine("Binded");
return _showDetailCheckCommand ?? (_showDetailCheckCommand = new RelayCommand<Check>((c) =>
{
Debug.WriteLine("Action!");
}));
}
}
In my XAML
<Page
x:Name="pageRoot"
...
<Maps:MapControl x:Name="Map" IsEnabled="False" Margin="0,8,0,8" MapServiceToken="******" LandmarksVisible="False" PedestrianFeaturesVisible="False" TrafficFlowVisible="True" ZoomLevel="16">
<!-- Incidents -->
<Maps:MapItemsControl ItemsSource="{Binding checks}">
<Maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Image Tag="{Binding}" Source="{Binding image_path}" Maps:MapControl.Location="{Binding geodata, Converter={StaticResource RoadsmartCoordinatesToGeoPointConverter}}" Maps:MapControl.NormalizedAnchorPoint=".5,.5" >
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding DataContext.ShowDetailCheckCommand, ElementName=pageRoot}" CommandParameter="{Binding}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
</DataTemplate>
</Maps:MapItemsControl.ItemTemplate>
</Maps:MapItemsControl>
</Maps:MapControl>
My output log does say:
'Binded'
But when I click on the image on the map the 'Action' is not executed.
Stupid of me.
I had the Map property isEnabled set to false.
Which is confusing because all the Map gestures work, but none of the binded XAML controls on top of it not.
Stupid mistake but nonetheless it might help people.
This is a pretty easy question I think , but I can't find the answer. I have an itemtemplate defined into a datatemplate. When an item is selected, I wanna trigger a command to select the name of my element and apply it somewhere else. For the moment the MouseDown event doesn't accept my command.
<ListView Margin="4" Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{Binding Path=ExistingStateInfos, ElementName=Window}"
SelectedItem="{Binding Path=SelectedStateInfo, ElementName=Window}" x:Name="statinfoListview">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type States:StateInfo}">
<TextBlock Text="{Binding Path=Name}" MouseDown="{x:Static MyWindow.ApplyStateInfoNameToStateCommand}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can't set a command directly to an event handler.
Use EventToCommand from the MVVM LightToolkit
<DataTemplate DataType="{x:Type States:StateInfo}">
<TextBlock Text="{Binding Path=Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<Command:EventToCommand Command="{Binding YourCommand, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
In fact ot was bulshit from me, I just add to do
<TextBlock Text="{Binding Path=Name}" MouseDown="{x:Static ApplyStateInfoNameToState_Click}" />
Sorry it's friday. Have a nice weekend :)