Situation: In MVVM pattern, I have some inputbindings on a listview which work only when the listview is focused. However, whenever user clicks, the listview goes out of focus and user is unable to execute the inputbindings.
Problem: I want to bring the focus on the listview (on button click) in a way that the inputbindings work.
What I tried:
I tried using attached property IsFocused (where I focus using UIElement.Focus() and/or Keyboard.Focus()) and binding it to a bool variable in the ViewModel which I would set using an ICommand.
I also tried a separate example where I can use the System.Windows.Input.Keyboard.Focus(item) method in the code behind (I mean the .xaml.cs file with the same name) to focus the listview and it works! But, I don't know how to implement the similar thing in a ViewModel which is connected using a d:DesignInstance attribute.
I believe that the mouseclick event is bubbled up and handled somewhere else which causes the list to unfocus as soon as I click it. Like, if I find a way to set the event as handled that will help, but again I don't know how to do that in a viewmodel. Here is my attached property :
FocusExtension.cs
public static class FocusExtension {
public static bool GetIsFocused(DependencyObject obj) {
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value) {
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e) {
var uie = (UIElement)d;
if ((bool)e.NewValue) {
uie.Focus();
}
}
}
XAML File:
<ListView
x:Name="lv"
Grid.Column="2" Margin="2" MinWidth="250" Height="400" ToolTip="the List"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding ListBindingInVM}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="False"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding }"
behaviour:ListViewAutoScroll.AutoScrollToEnd="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
>
<ListView.Style>
<Style TargetType="ListView" >
<Setter Property="ViewModels:FocusExtension.IsFocused" Value="{Binding ListFocused, Mode=TwoWay}"></Setter>
<!--The one below is not clean, but worked. However, list goes out of focus on click. -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ViewModels:FocusExtension.IsFocused" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<!--This command sets the ListFocused to true-->
<i:InvokeCommandAction Command="{Binding BringListToFocus }"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.InputBindings>
<!-- Bindings that don't work when list is not focused-->
<KeyBinding Modifiers="Control" Key="C" Command="{Binding CopyCommand}"/>
<KeyBinding Modifiers="Control" Key="V" Command="{Binding PasteCommand}"/>
</ListView.InputBindings>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy" Command= "{Binding CopyCommand}"></MenuItem>
<MenuItem Header="Paste" Command= "{Binding PasteCommand}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
The focus behavior that you describe is easily implemented from the codebehind, and doing so does not violate the MVVM pattern. Consider Josh Smith's post, below:
https://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090097
The use of a ViewModel here makes it much easier to create a view that
can display a Customer object and allow for things like an
"unselected" state of a Boolean property. It also provides the ability
to easily tell the customer to save its state. If the view were bound
directly to a Customer object, the view would require a lot of code to
make this work properly. In a well-designed MVVM architecture, the
codebehind for most Views should be empty, or, at most, only contain
code that manipulates the controls and resources contained within that
view. Sometimes it is also necessary to write code in a View's
codebehind that interacts with a ViewModel object, such as hooking an
event or calling a method that would otherwise be very difficult to
invoke from the ViewModel itself.
Related
I need to open a new view (item details) on mouse double click in ListView in UWP using MVVM. In WPF I used a command with a parameter and EventTrigger but Microsoft does not recommended to use it in UWP:
Triggers, EventTrigger, Actions and BeginStoryboard are not commonly used. These API mainly exist for compatibility in XAML originally used for Microsoft Silverlight...For events in control templates, use visual states and VisualStateManager.
As I understood it is used when you need to change visual state of the control but I need to open a new view.
How can I use VisualStateManager for my purpose?
There is how my XAML looked in WPF:
<ListBox x:Name="PersonsListControl" Grid.RowSpan="3" Grid.Row="0" Grid.Column="2"
ItemsSource="{Binding Path=PersonsProvider}"
ItemsPanel="{StaticResource PersonsListPanelTemplate}"
ItemTemplate="{StaticResource PersonsListItemTemplate}"
SelectedItem="{Binding SelectedPerson}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction
Command="{Binding GetPersonDetailsCommand}"
CommandParameter="{Binding SelectedPerson}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
In UWP you can use {x:Bind ...} :
<ListBox ...
DoubleTapped="{x:Bind HandleDoubleTapped}" />
And in your ViewModel just create a method :
public void HandleDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
// your logic
}
References :
DoubleTapped
ListBox
EDIT:
#JörgenSigvardsson pointed out that x:Bind do not bind directly to the DataContext and you should create a proxy property/properties to access particular data from your page.
More on that can be read here
So i need to call an event from one user control to do things in another. But to do that I would need to create an object in the control that subscribes to the event. Is there a way to do it without having an object?
Some xaml code.
This is in the control that needs to subscribe and change. Selector is a custom listbox with some styles.
<switch:Selector x:Name="ConfigSelector" Grid.Row="0" Grid.Column="0" SelectedConfigurationChangedEvent="SelectedConfigurationChangedEventOccured"></switch:Selector>
This is a button that should call an event and do things with the listbox showen previously in another user control.
<Button Name="button" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0" Width="80" Padding="5,3" Margin="10,10,0,10" Content="Example" Click="OnButtonClick"></Button>
My advice is to use Commands, they are the primae candidate for this in WPF
It's possible, for example if you want to change the content of button when the property of another button got changed , you can do this without having an object using Trigger:
Like In this example i am changing the content of one button on changed of IsEnabled property of another button.
<Button >
<Button.Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=AnotherButtonName , Path=IsEnabled}" Value="False">
<Setter Property="Content" Value="Hi Hello"/>
</Style.Triggers>
</Button.Style>
</Button>
Might be it helps you.
You have to expose that functionality of UserControl
Something like (dependency property):
public partial class MyUserControl : UserControl
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyUserControl),
new PropertyMetadata((d, a) => ((MyUserControl)d).TextChanged()));
private void TextChanged()
{
someTextBlock.Text = Text;
}
}
Now you can bind something to someTextBlock.Text in the window where MyUserControl is used, by using MyUserControl.Text.
I am trying to add an ItemsSource to a MenuItem while keeping the Command bound to my ViewModel (my Window's DataContext). So far, I haven't figured out a way to make it work. Before the ItemsSource is added, the binding is fine. The collection that I am trying to bind comes from a StaticResource. Can anybody help me out with this?
<MenuItem Command="{Binding OpenTeamPage}"
DisplayMemberPath="Name"
Header="Teams"
ItemsSource="{Binding Teams,
Source={StaticResource Container}}" />
I have tried using this and variations of it with no luck:
Command="{Binding OpenTeamPage,
RelativeSource={RelativeSource AncestorType=Window},
Mode=Default}"
If anybody could tell me how to use this ItemsSource while still binding my Command to my ViewModel, I would greatly appreciate it. I suppose I could put the Command in my Team model, but I would like to avoid that if possible.
EDIT : To clarify my problem, with the ItemsSource in place, the command in the ViewModel doesn't fire at all. Without the ItemsSource, the command fires. I would like to be able to have the ItemsSource and still be able to fire the command.
EDIT:
public class GameContainer
{
static GameContainer()
{
Teams = new ObservableCollection<Team>();
}
public static ObservableCollection<Team> Teams { get; set; }
}
In App.xaml:
<data:GameContainer x:Key="Container" />
The collection is populated when the program is started.
My goal once I get this working is to pass the selected team to the Viewmodel, hopefully via CommandParameter, and display info regarding the selected team.
EDIT: I was mistaken in my original post. A bound collection coming from the Viewmodel does not work either.
This is the behaviour of MenuItem, Item having Child MenuItem won't fire Command and it also should not as it does not make sense. But if you still want to fire a command on Parent Item click,there are two options
You can use Interactivity Triggers on your MenuItem to call command on MouseDown event like
<MenuItem
DisplayMemberPath="Name"
Header="Teams"
ItemsSource="{Binding Teams,
Source={StaticResource Container}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cmd:EventToCommand Command="{Binding OpenTeamPage}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
you can define a Attached Property for command and define the MenuItem MouseDown behaviour like
public static class MouseCommandBehavior
{
public static readonly DependencyProperty MouseDownCommandProperty =
DependencyProperty.RegisterAttached("MouseDownCommand",
typeof(ICommand),
typeof(MouseCommandBehavior),
new FrameworkPropertyMetadata(null, (obj, e) => OnMouseCommandChanged(obj, (ICommand)e.NewValue, false)));
public static ICommand GetMouseDownCommand(DependencyObject d)
{
return (ICommand)d.GetValue(MouseDownCommandProperty);
}
public static void SetMouseDownCommand(DependencyObject d, ICommand value)
{
d.SetValue(MouseDownCommandProperty, value);
}
private static void OnMouseCommandChanged(DependencyObject d, ICommand command)
{
if (command == null) return;
var element = (FrameworkElement)d;
element.PreviewMouseDown += (obj, e) => command.Execute(null);
}
}
}
and you can set this Property value on your menuItem
<MenuItem local:MouseCommandBehavior.MouseDownCommand="{Binding OpenTeamPage}"
DisplayMemberPath="Name"
Header="Teams"
ItemsSource="{Binding Teams,
Source={StaticResource Container}}">
MenuItem will not execute its command if it's not a leaf node. Only menu items that are leafs (items with no children) are executing a command.
This is probably done due to convention - when you click an items that has children you get the children shown immediately, otherwise there's a delay from mouse hover till children shown.
Although it's probably a bad idea (from UX point of view) to have command on a parent, it's possible:
<MenuItem DisplayMemberPath="Name"
Header="{Binding OpenTeamPage}"
ItemsSource="{Binding Teams, Source={StaticResource Container}}" >
<MenuItem.HeaderTemplate>
<DataTemplate>
<!--Probably need to make this button transparent-->
<Button Content="Teams"
Command="{Binding }"/>
</DataTemplate>
</MenuItem.HeaderTemplate>
<!--This style is for the children to fire the same command as the parent-->
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}, Path=Header}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Depending upon your design, you'd might need to style the button to be transparent.
I am using WPF and PRISM framework for my application. The pattern I am using is MVVM (Model - View - ViewModel) and I am trying to bring the MouseLeftButtonUp event from the code-behind in the View to the ViewModel (so the event will be according the MVVM rules). For now I have this:
View.xaml:
<DataGrid x:Name="employeeGrid" Height="250" Margin="25,0,10,0" ItemsSource="{Binding DetacheringenEmployeesModel}" IsReadOnly="True" ColumnHeaderStyle="{DynamicResource CustomColumnHeader}" AutoGenerateColumns="False" RowHeight="30">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding EmployeeGrid_MouseLeftButtonUp}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
View.xaml.cs (code-behind):
public partial class UC1001_DashBoardConsultants_View
{
public UC1001_DashBoardConsultants_View(UC1001_DashboardConsultantViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
ViewModel.cs:
public void EmployeeGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// insert logic here
}
The main idea is, when I click on a cell in the DataGrid, the event will fire. I first tried it in the code behind, and it worked. I got so far with the EventTriggers, but when I debug and click on a cell, my debugger doesn't come into the method.
Does anyone have an idea how to fix this? Thanks in advance!
PS: Does it also work with the (object sender) parameter when I do it like that? Because I need the DataGrid in my ViewModel to get the ActiveCell I just clicked on.
EDIT:
The event-binding worked with the Command!
I have this in my DataGrid:
<DataGridTextColumn Header="Okt" Width="*" x:Name="test" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Tag" Value="{Binding Months[9].AgreementID}"/>
How can I bind the Tag property to the ViewModel? I know it's already bound from the ViewModel, but as you can see the value comes from an Array/List and per column the value is different.
InvokeCommandAction requires the ICommand to be bound not an event handler as you've bound (EmployeeGrid_MouseLeftButtonUp).
So you can introduce a command in ViewModel and bind to it:
View Model:
public ICommand SomeActionCommand { get; set; }
XAML:
<i:InvokeCommandAction Command="{Binding SomeActionCommand}" />
I am having some difficulties binding a command (ICommand) to the MouseBinding of a ListView.
I used this piece of XAML code to test the different mouse gestures:
<ListView.InputBindings>
<MouseBinding Command="{Binding OpenSOACommand}" Gesture="LeftClick" />
<MouseBinding Command="{Binding OpenSOACommand}" Gesture="MiddleClick" />
<MouseBinding Command="{Binding OpenSOACommand}" Gesture="LeftDoubleClick" />
</ListView.InputBindings>
The LeftClick and LeftDoubleClick gestures aren't triggered, yet the MiddleClick mouse binding works perfect (I have tested the mouse bindings one at a time as well...).
Is there a difference in the way the LeftDoubleClick and MiddleClick Gesture is handled? And if there is, how can I bind my ICommand to the LeftDoubleClick gesture?
Thanks!
The default Click event for the ListView is marking the event as handled. Try using PreviewLeftClick and PreviewLeftDoubleClick instead
EDIT
Since MouseBindings does not contain a PreviewLeftClick or PreviewLeftDoubleClick, try using the AttachedCommandBehavior code found here which allows you to attach a Command to just about any Event
For example,
<ListView local:CommandBehavior.Event="PreviewMouseDown"
local:CommandBehavior.Command="{Binding OpenSOACommand}" />
This is because your ListViewItems of your ListView will swallow your LeftClick events and convert them into nice SelectionChanged events. Since the ListViewItems will not respond to MiddleClick, this will work as expected.
You might want to get 'in front' of this click by handling the matching Preview equivalent of the event.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseDoubleClick" Handler="OnItemDoubleClick"/>
</Style>
</ListView.ItemContainerStyle>
And invoke the command in the handler:
private void OnItemDoubleClick(object sender, MouseButtonEventArgs e)
{
OpenSOACommand.Execute(null, this);
}