Wpf data grid CurrentCell property fires only once MVVM - c#

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.

Related

WPF ComboBox will not trigger an event on initializing

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.

Binding cascading combobox itemsource in wpf datagrid using mvvm

How to bind a 2nd dropdown based on first dropdown selected value of first dropdown using mvvm
Here is the class strcture
List<Location> Locations; //Application global cached data
List<Room> Room; //Application global cached data
class Location {LocationId, Name ....}
class Room{RoomId, Name, LocationId...}
XAML
<DataGridTemplateColumn Header="Property Name">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="LocationsComboBox"
ItemsSource="{Binding Path=DataContext.Locations, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValue="{Binding PropertyId, UpdateSourceTrigger=PropertyChanged}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Room Number-->
<DataGridTemplateColumn Header="Room Number">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="RoomComboBox"
ItemsSource="{Binding Path=DataContext.Rooms, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="RoomName" SelectedValuePath="RoomId"
SelectedValue="{Binding NewRoomId, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.PropertyChangedCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Did you use INotifypropertychanged ? you should implement INotifyPropertyChanged and change your child list when parent was changed
Use ObservableCollection<Room> instead of List (this will cause the second combobox to update when the first combo box changes the location which in turn causes the room collection to change.
Use ObservableCollection<Location> also. Your locations might not ever change, but this is simply good MVVM form.
I prefer Master Slave/Details way of combo box.
U can find Here
But in your Case
The binding for Room ComboBox should be from Code Behind on the basis of selected LocationID.
the below binding
ItemsSource="{Binding Path=DataContext.Rooms..
should be something like this
ItemsSource="{Binding Path=DataContext.RoomsInSelectedLocation
and in ViewModel
IEnumerable<Room> RoomsInSelectedLocation
{
return Rooms.where(r => r.LocationId == SelectedLocationId);
}
evaluate this every time the Location Combo selected item changes.

ListView item click event MVVM

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.

Listbox selection hides other mouse event

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}"/>

Binding Command to element in DataGrid

I have a table with a with a column containing a bool value that I have put a checkbox in. I am trying to bind a command to the checkbox to where when I check it, it runs a command in the View Model. I'm using an MVVM structure. Here is what I have attempted so far.
<DataGrid
IsReadOnly="True"
Margin="0,10,0,0"
ItemsSource="{Binding Diary.Diaries}"
AutoGenerateColumns="False"
MinHeight="200"
SelectionMode="Single"
SelectionUnit="FullRow"
IsSynchronizedWithCurrentItem = "True"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Flagged">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding Flagged}" Command="{Binding Diary.FlagDiary}">
</CheckBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn >
<DataGridTextColumn Header="Diary ID" Binding="{Binding DiaryID}" />
</DataGrid.Columns>
</DataGrid>
And here is the RelayCommand in the ViewModel.
FlagDiary = new RelayCommand(() =>
{
Debug.WriteLine("Test");
});
I have been unable to get it to run the RelayCommand. Any idea what I'm doing wrong?
The Checkbox's DataContext would be set to one of the objects in the DataGrid's ItemSource (one of the Diary.Diaries entries). That makes the command binding invalid.
You will need to do a relative source binding so you can get to Diary.FlagDiary. Here is one way of doing that (given the xaml you posted above).
<CheckBox IsChecked="{Binding Flagged}"
Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.Diary.FlagDiary}" />
Once the command fires, you will probably want to know which Diary entry the checkbox was for... right? To do that, add a CommandParameter binding to the Checkbox. Now, the binding looks like this:
<CheckBox IsChecked="{Binding Flagged}"
Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.Diary.FlagDiary}"
CommandParameter="{Binding}" />
Your relay command code might have to change too. Probably something like this?
FlagDiary = new RelayCommand((parameter) =>
{
Debug.WriteLine(parameter.ToString());
});

Categories

Resources