Hello Stackoverflowers,
I have a System.Windows.Control.ListBox. It's doing a great job but i would like to had a few behaviours when i select certain types of items.
I can't do it in the bind property for SelectedItem because my Listbox's View Model (Foo) doesn't know all the needed datas for the work i want (some coming from another ViewModel : Bar).
My two mentioned ViewModel are field of a bigger class Zot, in order for Zot to access the content of both Foo and Bar
I foward click event in Foo and Bar to Zot using Interaction.Triggers, EventTrigger and InvokeCommandAction. It's working great for Bar (which is a canvas). However i have trouble with the Listbox.
After testing events SelectionChanged, MouseDown and Click, it appears that MouseDown is triggered if I click on the grid wrapping the listbox but not when i click on the ListBox. It feels like the embedded selection in the Listbox is conflicting with other events.
Anyone got any idea to do specific actions depending on the selected item, in a different viewmodel ?
Thanks a lot
EDIT :
Here is the XAML for the Listbox (in ToolboxView.xaml)
d:DataContext="{d:DesignInstance viewModels:ToolboxViewModel}">
<Grid>
<ListBox
ItemsSource="{Binding Tools}"
SelectedItem="{Binding SelectedTool}"
x:Name="ToolView" >
<ListBox.ItemTemplate>
<DataTemplate DataType="interfaces:IBuilder">
<TextBlock
FontWeight="DemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Here is the event on the Listbox, from the main window xaml (which view model holds the listbox view model, i explain why below). However the event is never triggered. Later in the same file, 3 similar event works perfectly (on a canvas). I tried to use MouseDown instead of SelectionChanged, it is triggered when i click in the grid containing the listbox but isn't trigger when i click listbox.
(in MainWindow.xaml)
<DockPanel>
<views:ToolboxView DockPanel.Dock="Left"
Width="120"
IsHitTestVisible="True"
DataContext="{Binding ToolBoxViewModel}"
x:Name="ToolboxView">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=DataContext.SelectionChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding ElementName=ToolboxOverlayView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Now what i called "embeded selection" is the behaviour of the Listbox where i can highlight an element inside the listbox and select it. This works perfectly with the code above (i can select my tools, the property binded in ViewModel change accordingly). What i'm trying to do is firing the SelectionChanged event to do special work when a certain category of elements inside the listbox are selected.
I could do this in the setter of the property binded to Listbox's ItemSelected but the work to do need datas unknown from the listbox view model, which is why i have a mainwindow view model that holds the view model of the listbox and i try to get the SelectionChanged event in the main window view model (and another view model).
Tell me if it's not clear please.
You're trying to set a SelectionChanged event in your ToolboxView that does not know any SelectionChanged event.
You could create two DP in ToolboxView that stores the command and its parameter:
#region SelectionChangedCommand
public ICommand SelectionChangedCommand
{
get { return (ICommand)GetValue(SelectionChangedCommandProperty); }
set { SetValue(SelectionChangedCommandProperty, value); }
}
private readonly static FrameworkPropertyMetadata SelectionChangedCommandMetadata = new FrameworkPropertyMetadata {
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.Register("SelectionChangedCommand", typeof(ICommand), typeof(ToolboxView), SelectionChangedCommandMetadata);
#endregion
#region SelectionChangedCommandParameter
public Object SelectionChangedCommandParameter
{
get { return (Object)GetValue(SelectionChangedCommandParameterProperty); }
set { SetValue(SelectionChangedCommandParameterProperty, value); }
}
private readonly static FrameworkPropertyMetadata SelectionChangedCommandParameterMetadata = new FrameworkPropertyMetadata {
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
public static readonly DependencyProperty SelectionChangedCommandParameterProperty =
DependencyProperty.Register("SelectionChangedCommandParameter", typeof(Object), typeof(ToolboxView), SelectionChangedCommandParameterMetadata);
#endregion
Then in the ToolboxView.xaml:
<Grid>
<ListBox
ItemsSource="{Binding Tools}"
SelectedItem="{Binding SelectedTool}"
x:Name="ToolView" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=SelectionChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type ToolboxView}}}"
CommandParameter="{Binding Path=SelectionChangedCommandParameter, RelativeSource={RelativeSource AncestorType={x:Type ToolboxView}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate DataType="interfaces:IBuilder">
<TextBlock
FontWeight="DemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And use it in MainWindow.xaml:
<views:ToolboxView DockPanel.Dock="Left"
Width="120"
IsHitTestVisible="True"
DataContext="{Binding ToolBoxViewModel}"
x:Name="ToolboxView"
SelectionChangedCommand="{Binding Path=DataContext.SelectionChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectionChangedCommandParameter="{Binding ElementName=ToolboxOverlayView}"/>
Related
I have a datagrid that looks like:
<DataGrid x:Name="Applications" CanUserResizeColumns="False" CanUserResizeRows="False"
AutoGenerateColumns="false" CanUserAddRows="false" ItemsSource="{Binding Applications}"
SelectionMode="Single"
CurrentCell="{Binding CellInfo, Mode=TwoWay}">
And I have a question about CurrentCell, it is binded to poeprty in view model that looks like:
private DataGridCellInfo cellInfo;
public DataGridCellInfo CellInfo
{
get => cellInfo;
set
{
cellInfo = value;
OnPropertyChanged();
if (cellInfo.Column.DisplayIndex == 1)
{
var selectedApplication = (ExtendedApplicationFile)cellInfo.Item;
ExpandAppDetailsCommand.Execute(selectedApplication);
}
}
}
And what it does, it sets correct item and sends it to command that will expend and hide row details window.
Problem is if I click once property is set and it will expand, but when I click second time on same cell, property is not setting and details row is not collapsing. It will work again when I click other cell and get back to it, but that is not I am aiming for.
Basing on information in comment I came up with simple solution, Ive added cell template with event trigger:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Name}" Width="350">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<i:InvokeCommandAction Command="{Binding DataContext.ExpandAppDetailsCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Using thin each time I click cell event fires and toggles details view.
I have the following situation:
1. I have Window Load event - which gets the SourceItems for a ComboBox
2. ComboBox have EventTrigger for Selection Changed and the following XAML:
<ComboBox x:Name="uxEnvironmentsComboBox"
ItemsSource="{Binding Environments}" Width="90" Margin="0,10,160,0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
IsSynchronizedWithCurrentItem="True" SelectedIndex="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectEnvironment}"
CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Everything works, the Window Load properly, the List is populated in the ComboBox, but when the default selection is made my <i:EventTrigger EventName="SelectionChanged"> does not trigger and the rest of the application configuration is not triggered unless I make a manual selection change ?!
SelectEnvironment should be a property that you bind to:
<ComboBox x:Name="uxEnvironmentsComboBox"
ItemsSource="{Binding Environments}" Width="90" Margin="0,10,160,0"
SelectedItem="{Binding SelectEnvironment}"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
IsSynchronizedWithCurrentItem="True" SelectedIndex="0">
</ComboBox>
You can invoke any command in the setter of this property if you want to:
private Environment _selectEnvironment;
public Environment SelectEnvironment
{
get { return _selectEnvironment; }
set
{
_selectEnvironment = value;
//invoke command
YourCommand.Execute(_selectedEnvironment);
}
}
And if you want to invoke the command or set the property initially, you could for example do this in the constructor of the view model. You don't need to use an InvokeCommandAction.
I have a MenuItem with ListView inside. What I want is when I click on a ListView item, some command fires. Here is my code:
<MenuItem Header="?">
<ListView ItemsSource="{Binding CommentTemplateList}" BorderThickness="0" SelectedItem="{Binding SelectedCommentTemplate, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding PasteTemplate}"
CommandParameter="{Binding SelectedCommentTemplate}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}" ToolTip="{Binding Description}" HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</MenuItem>
Everything is ok, but command PasteTemplate fires only when selection is changed, and I need to it fire every time I click on the item. If I change EventName to one from the list (https://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.selector.aspx), for example MouseDown, the command does not fire at all.
To accomplish this, while respecting the MVVM architecture, the best way is to add the specific behavior to your xaml code as follows;
<ListView x:Name="ListView"
ItemsSource="{x:Bind ViewModel.SampleItems, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=OneWay}"
IsItemClickEnabled="True">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="ItemClick">
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ItemClickCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ListView>
And in your View Model, after declaring an IComand property as follows,
public ICommand ItemClickCommand
{
get
{
if (_itemClickCommand == null)
{
_itemClickCommand = new RelayCommand<ItemClickEventArgs>(OnItemClick);
}
return _itemClickCommand;
}
}
Define the command as if you were handling the event in the code behind as follows;
private void OnItemClick(ItemClickEventArgs args)
{
ListDataItem item = args?.ClickedItem as ListDataItem;
//DO what ever you want with the Item you selected in the click
}
Note: RelayCommand is used to handled commands using the MVVMLight Framework.
You could handle the PreviewMouseDown event of the ListViewItem as suggested here:
WPF MVVM Light Multiple ListBoxItems bound to same object
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
</Style>
</ListView.ItemContainerStyle>
..
</ListView>
If you don't want to invoke the command of the view model from the code-behind you could wrap the same functionality in an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf.
There is more information an example on the link above.
If you wanna use 'SelectionChanged', You can reset the selection after your code. Just add that on your PasteTemplate
if(((ListView)sender).SelectedIndex == -1)return;
//your code
((ListView)sender).SelectedIndex = -1;
So, after your code, ListView has no selected elements. So if you click it again, the selection is changed again and code fires again.
Note: you can use MouseDown for it too, but it's a little tricky. For example, if user clicks none of your items but somewhere else inside your ListView like this, it fires again with your current selection.
This label is present in a User Control and I am binding this Label's Content property to Window's datacontext. I want to execute a ICommand whenever a Content of this Label changes. Property it is binded to is in Parent's ViewModel. While I have a ICommand in user control's viewmodel.
<Label Style="{StaticResource LabelStyle}"
FontSize="28" HorizontalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window},
Mode=FindAncestor}, Path=DataContext.CurrentTag}">
</Label>
I found a cleaner approach that fits with MVVM pattern.
Interactivity is coming from below namespace
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Setting NotifyOnTargetUpdated=True, enables TargetUpdated event to trigger where I bound my ICommand
<Label Style="{StaticResource LabelStyle}" FontSize="28" HorizontalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}
,Mode=FindAncestor}
, Path=DataContext.CurrentTag, NotifyOnTargetUpdated=True}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TargetUpdated">
<i:InvokeCommandAction Command="{Binding SubmitCommand}"
CommandParameter="{Binding
RelativeSource={RelativeSource AncestorType={x:Type Window}
,Mode=FindAncestor}, Path=DataContext.CurrentTag}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
The Label has no ContentChanged event to handle.
What you could do is to use a DependencyPropertyDescriptor and hook up an event handler using its AddValueChanged method programmatically:
public partial class MyUserControl : UserControl
{
public MyUserControl ()
{
InitializeComponent();
DataContext = new YourViewModelClass();
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(Label.ContentProperty, typeof(Label));
if (dpd != null)
{
dpd.AddValueChanged(label, OnLabelContentChanged);
}
}
private void OnLabelContentChanged(object sender, EventArgs e)
{
var vm = this.DataContext as YourViewModelClass;
vm.YourCommand.Execute(null);
}
}
Handling changes to dependency properties in the view: https://blog.magnusmontin.net/2014/03/31/handling-changes-to-dependency-properties/
<Label x:Name="label"
FontSize="28" HorizontalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window},
Mode=FindAncestor}, Path=DataContext.CurrentTag}">
</Label>
As mentioned before, Label has no ContentChanged event.
However, when I needed this property to work I have used SizeChanged Event instead.
What I did was whenever I change the Label's Content, I have changed the width of it with a value that close to original width.
Since Label does not show the edges, It does not matter that how much your Label long, unless you do not have any other tool near it.
I know this is not the exact and formal solution for this problem, but It saved my day.
Label has no ContentChanged event as already said but TextBox does have a TextChanged event.
If you disable the IsHitTestVisible and turn the BorderBrush and Background invisible then it looks almost the same as a Label and is not writeable so it also functions the same.
I have an MVVM application and part of the functionality is issuing a command in my viewmodel. The control which is bound to the command happens to reside in the rows of a DataGrid. Here is some XAML for that:
<DataGridTemplateColumn Width="25">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--<base:DeleteButton HorizontalAlignment="Center" ToolTip="Delete Polygon"
Visibility="{Binding Path=CanDeleteFromUI, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding Path=DataContext.DeletePolygonCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding}"/>-->
<CheckBox Template="{StaticResource RemoveXButtonTemplate}" Margin="0,0,3,0" HorizontalAlignment="Center" ToolTip="Delete Polygon" Cursor="Hand" Visibility="{Binding Path=CanDeleteFromUI, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding Path=DataContext.DeletePolygonCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}" >
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Notes: For various reasons, the "button" is actually a checkbox.
This works just fine as coded. Note there is a commented-out user control, which I can't get to work properly, but the "real" checkbox works just fine.
Here is XAML for the user control:
<UserControl x:Class="Athena.Infrastructure.DeleteButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" >
<Grid>
<CheckBox Command="{Binding Path=Command}" CommandParameter="{Binding Path=CommandParameter}" Cursor="Hand">
<CheckBox.Template>
<ControlTemplate>
<Border Width="14" Height="14" Background="#00000000" Margin="2,0,2,0">
....
....
</ControlTemplate>
</CheckBox.Template>
</CheckBox>
</Grid>
</UserControl>
I didn't include all the XAML for the control template, as everything displays just fine.
Here is the code-behind:
public partial class DeleteButton : UserControl
{
public DeleteButton()
{
InitializeComponent();
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(DeleteButton));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set{SetValue(CommandProperty, value);}
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(DeleteButton));
public object CommandParameter
{
get { return (object) GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
I didn't show my VM code, but it contains the usual 3 methods, Command, CanExecute and Method. When I use the checkbox, the Command executes once (to initialize the Method), and thereafter the Method does fire when I click the checkbox.
When I use the user control instead of the normal checkbox, the Command in my VM fires once as expected. Thereafter, the method does not fire at all.
It seems that this is a common thing, to use a User Control instead of a native control, right? I can't figure out why my VM method will not fire using the User Control.
Can anybody shed some light on this for me? Thanks so much...
The first things to check in this case are the binding errors in the Visual Studio console, it can help you to target the error.
I think your problem comes from the Binding inside your UserControl. You try to bind your CheckBox's Command to the Command property of your DataContext. Instead, you have to bind it to the Command property of your UserControl, using ElementName:
<UserControl x:Class="Athena.Infrastructure.DeleteButton"
...
x:Name="deleteButton">
<Grid>
<CheckBox Command="{Binding Path=Command, ElementName=deleteButton}"
CommandParameter="{Binding Path=CommandParameter, ElementName=deleteButton}"
Cursor="Hand">
...
</CheckBox>
</Grid>
</UserControl>
Or RelativeSource:
<UserControl x:Class="Athena.Infrastructure.DeleteButton"
...
xmlns:local="clr-namespace:Athena.Infrastructure">
<Grid>
<CheckBox Command="{Binding Path=Command, RelativeSource={RelativeSource AncestorType={x:Type local:DeleteButton}}}"
CommandParameter="{Binding Path=CommandParameter, RelativeSource={RelativeSource AncestorType={x:Type local:DeleteButton}}}"
Cursor="Hand">
...
</CheckBox>
</Grid>
</UserControl>