Populate a second DataGrid once I double click on a row WPF - wpf

I am still a little new to wpf and MVVM. I am trying to code a solution without breaking that pattern. I have two (well three, but for the scope of this question just two) DataGrids. I want to double click on the row of one, and from that load data Into the second DataGrid (ideally I would spin up a second thread that would load the data). So far I can get a window to pop up when I double click on a row. I throw the code for the event into the code behind for the xaml. To me that seems very windows formish. Somehow or the other I feel like that breaks the pattern a great deal.
private void DataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) {
if (popDataGrid.SelectedItem == null) {
return;
}
var selectedPopulation = popDataGrid.SelectedItem as PopulationModel;
MessageBox.Show(string.Format("The Population you double clicked on has this ID - {0}, Name - {1}, and Description {2}", selectedPopulation.populationID, selectedPopulation.PopName, selectedPopulation.description));
}
That is the code for the event in the code behind and here is the grids definition in the xaml:
<DataGrid ItemsSource="{Binding PopulationCollection}" Name="popDataGrid"
AutoGenerateColumns="False" RowDetailsVisibilityMode="VisibleWhenSelected"
CanUserAddRows="False" Margin="296,120,0,587" HorizontalAlignment="Left" Width="503" Grid.Column="1"
MouseDoubleClick="DataGrid_MouseDoubleClick">
</DataGrid>
I am thinking this code should go in the MainWindowViewModel. So I am attempting to create a command:
public ICommand DoubleClickPopRow { get { return new DelegateCommand(OnDoubleClickPopRow); }}
and the same event handler:
private void OnDoubleClickPopRow(object sender, MouseButtonEventArgs e) {
}
But the ICommand is throwing an exception when it returns the DelegateCommand(OnDoubleClickPopRow).
Well, one can plainly see that the number of arguments doesn't match. I know I am doing something wrong, but I am not quite sure what it is. I will continue to research this but any help you guys can give would be greatly appreciated.

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<DataGrid ItemsSource="{Binding PopulationCollection}" Name="popDataGrid"
AutoGenerateColumns="False" RowDetailsVisibilityMode="VisibleWhenSelected"
CanUserAddRows="False" Margin="296,120,0,587" HorizontalAlignment="Left" Width="503" Grid.Column="1" SelectedItem="{Binding ItemInViewModel}"></DataGrid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Save_Bid}" />
</i:EventTrigger>
</i:Interaction.Triggers>
You can add this to your DataGrid and add your code in your viewmodel.
Now that we have a selected item bound to an item in our view model we can use that item to know when we can fire the even we want as well as what item to use when the event is fired When the event can be fired
bool Can_Fire_Event()
{
if(ItemInViewModel != null)
{ return true; } else { return false; }
}
private RelayCommand _saveBid;
public ICommand SaveBid
{
get
{
if (_saveBid == null)
{
_saveBid = new RelayCommand(param => Save_Bid(), param => Can_Fire_Event());
}
return _saveBid;
}
}
public void Save_Bid()
{
//Open your new Window here, using your "ItemInViewModel" because this event couldn't be fired from your datagrid unless the "ItemInViewModel" had a value assigned to it
}

Related

Why does the ComboBox in my DataGrid column reset itself to null?

In my WPF application, I am developing a fairly straightforward page that allows either creating a new object or choosing one from a combo box, then editing the object.
One of the parts of the object that is editable is a related database table in a one-to-many relationship, so for that piece I used a DataGrid. The DataGrid itself has a data-bound ComboBox column, as you can see here:
<DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True"
CanUserAddRows="False" CanUserDeleteRows="True"
ItemsSource="{Binding Path=No.Lower_Assy}"
DataGridCell.Selected="dgAssy_GotFocus">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Number & Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.ComboSource, RelativeSource={RelativeSource AncestorType=Page}}"
SelectedValuePath="bwk_No"
SelectedValue="{Binding Path=fwf_Higher_N, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Number}"/>
<TextBlock Text="{Binding Path=Type}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- other text columns omitted -->
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Delete" Click="btnDeleteHigherAssy_Click" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Code behind:
private void dgAssy_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource.GetType() == typeof(DataGridCell))
{
// Starts the edit on the row
DataGrid grd = (DataGrid)sender;
grd.BeginEdit(e);
}
}
And for the save button:
private void btnSave_Click(object sender, RoutedEventArgs e)
{
if (CanUserEdit())
{
if (string.IsNullOrWhiteSpace(model.Data.Error))
{
repo.Save(model.Data);
StatusText = STATUS_SAVED;
model.CanSave = false;
// This is the data source for the main combo box on the page
model.ComboSource = repo.GetData();
// Set the combo box's selected item, in case this is a new object.
// cboNo is the main combo box on the page which allows selecting
// an object to edit
// Apparently setting SelectedItem directly doesn't work on a databound combo box
int index = model.ComboSource.ToList().FindIndex(x => x.bwk_No == model.Data.bwk_No);
cboNo.SelectedIndex = index;
}
else
{
MessageBox.Show("Invalid data:\n" + model.Data.Error, "Cannot save");
}
}
}
The problem
When I choose an item from the combo box in the data grid, it seems to work until I click on the save button. Then two things happen:
The combo box's selected item is set to null, blanking out the combo box.
As a result of (1), the save button is re-enabled because the data has changed. (The save button is bound to model.CanSave, which as you can see is set to false in the button handler; it is set to true by a property change event handler if there are no data errors.)
Why is it being reset? I've followed the code flow closely and can see the property change event for the combo box's backing field (fwf_Higher_N) being handled, and it appears to somehow come from the line model.ComboSource = repo.GetData();, but the stack only shows [external code] and I don't see why that line would modify an existing object.
The model class
// Names have been changed to protect the innocent
private class MyDataViewModel : INotifyPropertyChanged
{
private DbData _Data;
public DbData Data
{
get { return _Data; }
set
{
_Data = value;
OnPropertyChanged("Data");
}
}
private IQueryable<MyComboModel> _ComboSource;
public IQueryable<MyComboModel> ComboSource {
get { return _ComboSource; }
set
{
_ComboSource = value;
OnPropertyChanged("ComboSource");
}
}
private bool _CanSave;
public bool CanSave
{
get { return _CanSave; }
set
{
_CanSave = value;
OnPropertyChanged("CanSave");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Your description of what is going on and your markup doesn't quite match. I'm going to make some assumptions, such as that Page.DataContext is an instance of MyDataViewModel.
I'm sorry to say it, but a SSCCE would do wonders here. I strongly suggest when anyone gets into situations where they are elbow deep in code they don't quite understand that they break out what they are attempting to do and create a minimal prototype that either exhibits the same behavior, or that helps you learn what's going wrong. I've made 500+ prototypes in the past five years.
As for this situation, you refer to a ComboBox named cboNo in btnSave_Click, but I don't see that in the xaml. This ComboBox's ItemSource appears to be bound to MyDataViewModel.ComboSource.
In addition, all ComboBoxes in the DataGrid also appear to be bound to the model's ComboSource. And, in the button handler event, you change what is in the property:
// This is the data source for the main combo box on the page
model.ComboSource = repo.GetData();
This fires PropertyChanged, and every ComboBox bound to this property will be updated. That means not only cboNo but also every ComboBox in the DataGrid.
It is expected behavior that, when ComboBox.ItemsSource changes, if ComboBox.SelectedItem is not contained within the items source, that SelectedItem is nulled out.
I just spun up a prototype (501+) and it appears that if the IEnumerable that the ComboBox is bound to changes, but the elements in the IEnumerable do not, then SelectedItem is not nulled out.
var temp = combo.ItemsSource.OfType<object>().ToArray();
combo.ItemsSource = temp;
So, within the btnSave_Click event handler, you change this ItemsSource, which probably does not have the same instances that are already in the combo, thus nulling out SelectedItem for all ComboBoxes bound to this property, and then only update cboNo's SelectedIndex.
Now, as for what to do about it...
Well, not sure. From the rest of your code, it appears you need to do some more codebehind work to make sure only the necessary ComboBoxes have their sources updated...

Databinding not updating ListBox completely

I'm using the MVVM pattern and have a databound Listbox that isn't updating completely.
There is a modelview that contains an Observable collection of Machines which is bound to the list:
<ListBox Name="MachinesList"
Height="300"
Width="290"
DataContext="{Binding Path=AllMachines}"
SelectionMode="Single"
ItemsSource="{Binding}" SelectionChanged="MachinesList_SelectionChanged"
HorizontalAlignment="Right"
>
The collection AllMachines Contains an observable collection of MachineModelViews which are in turn bound to a MachineView that presents the name and location of the machine:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Label Name="NameLabel" Content="{Binding Path=Name}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Width="50" />
<Label Content="Location:" Width="120"
HorizontalAlignment="Right"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Target="{Binding ElementName=locationLabel}"
/>
<Label Content="{Binding Path=Location.Name}" Name="locationLabel" HorizontalContentAlignment="Center" Width="60"/>
</StackPanel>
The problem:
When values are added to the collection things update okay. When a machine is deleted however only the Label bound to Location.Name updates the other two remain in the listbox. I've confirmed that the collection is actually updating and removing the MachineModelView Correctly but some how the label with it's name and the "label label" with "Location:" continues to exist until the application is restarted:
before:
after delete:
after app restart:
The delete button calls a command which removes the item from the private member that backs the AllMachines property and from the repository (which ultimately plugs into a database via Entity Framework):
RelayCommand _deleteCommand;
public ICommand DeleteCommand
{
get
{
if (_deleteCommand == null)
{
_deleteCommand = new RelayCommand(
param => this.Delete(),
null
);
}
return _deleteCommand;
}
}
void Delete()
{
if (_selectedMachine != null && _machineRepository.GetMachines().
Where(i => i.Name == _selectedMachine.Name).Count() > 0)
{
_machineRepository.RemoveMachine(_machineRepository.GetMachines().
Where(i => i.Name == _selectedMachine.Name).First());
_allMachines.Remove(_selectedMachine);
}
}
Note: There can only be one item with a name in AllMachines (this is handled by the add logic in the repository and command itself) so removing the "First" one should be fine.
The AllMachines property:
public ObservableCollection<MachineViewModel> AllMachines
{
get
{
if(_allMachines == null)
{
List<MachineViewModel> all = (from mach in _machineRepository.GetMachines()
select new MachineViewModel(mach, _machineRepository)).ToList();
_allMachines = new ObservableCollection<MachineViewModel>(all);
}
return _allMachines;
}
private set
{
_allMachines = value;
}
}
The selection changed event handler:
private void MachinesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is MachineViewModel)
((MachinesViewModel)this.DataContext).SelectedMachine = (MachineViewModel)e.AddedItems[0];
}
If 'AllMachines is your actual observable collection, I would bind the itemssource to that instead of binding it to ur datacontext, and then bind ur viewmodel as your datacontext. It may also be helpful to call the updatesourcetrigger after your itemssource bind.
DataContext="{Binding Path=YourViewModel}"
SelectionMode="Single"
ItemsSource="{Binding AllMachines, UpdateSourceTrigger="PropertyChanged"}"
And you can use that updatesourcetrigger on some of your labels as well where you are binding. Other than that, you xaml looks ok, hard for anyone to really say until they see everything.
_machineRepository.RemoveMachine(_machineRepository.GetMachines().
Where(i => i.Name == _selectedMachine.Name).First());
AllMachines.Remove(_selectedMachine);
Instead of field _allMachines.Remove remove directly from the property AllMachines like
AllMachines.Remove
I hope this will help.
if you code the way you said here, then all should work. pls post the missing code pieces:
your AllMaschines Property
your MachinesList_SelectionChanged event <-- when doing MVVM you almost not need this
pls debug your Delete() method just to be sure that: _allMachines.Remove(_selectedMachine); is hit and the machine is really removed from your collection.
I found my problem, really stupid: I have an event that fires when the repository updates. Before I only had an add command and I was adding the ability to delete when this problem came up. Turns out the event was being handled by the MachinesModelView to update it's internal variable that is the source for the AllMachines property:
void OnMachineAddedToRepository(object sender, MachineAddedOrRemovedEventArgs e)
{
var viewModel = new MachineViewModel(e.NewMachine, _machineRepository);
this.AllMachines.Add(viewModel);
}
I modified the event arguments to toggle between add and delete but forgot to update the event handler. Now it does work:
void OnMachineAddedOrRemovedFromRepository(object sender, MachineAddedOrRemovedEventArgs e)
{
if (e.Added)
{
var viewModel = new MachineViewModel(e.NewMachine, _machineRepository);
this.AllMachines.Add(viewModel);
}
else if (AllMachines.Where(i => i.Name == e.NewMachine.Name).Count() > 0)
AllMachines.Remove(AllMachines.Where(i => i.Name == e.NewMachine.Name).First());
}
What made this so hard to track down is that the extra item only lived very briefly until the AllMachines.Remove part of the Delete command ran. So checking the pre-delete count, and the post delete count looked right it was the guts in the middle when the item is being deleted from the repository that fired off an event that added the item back and left it in this weird state. Kind of odd that it consistently only had the location part of the MachineModelView floating around. I now just let the event handler add or remove the items from the _allMachines variable and don't touch it explicitly in the delete command (the delete command just deletes it from the repository and lets the UI variables catch up in the fraction of a second it takes for the event to trigger). Thanks a bunch guys for your help.

DataGrid Looses Focus When Delete Key is Pressed

I'm doing MVVM where a DataGrid is bound to an ObservableCollection with a DeleteItemCommand hooked up to the DataGrid.InputBindings as follows:
<DataGrid.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteItemCommand}" />
</DataGrid.InputBindings>
The item and row are removed when the user hits the delete key but the grid looses focus. You have to click or tab to the grid so it regains focus before hitting Delete to remove another row (pretty freaking annoying). I tried setting the DataGrid.CanUserDeleteRows="False" but it doesn't make any difference.
I replaced the DataGrid with a ListView and the ListView retains focus.
Is this a bug with the DataGrid or am I doing something wrong? Peace and love, peace and love!
I solved this by using the built in functionality of WPF DataGrid. The grid handles removing items by default if the underlying collection is editable (if the collection is dedicated to this purpose that's no problem, otherwise an intermediate collection can be added...). I avoided any key bindings and just set up the grid like this:
<DataGrid ItemsSource="{Binding InvoiceItems}" IsReadOnly="False" CanUserDeleteRows="True" CanUserAddRows="False">
The ItemsSource collection is of type BidningCollection<>
In my ViewModel (my DataContext) I add a handler for CollectionChanged event:
InvoiceItems.CollectionChanged += InvoiceItemsCollectionChanged;
And implement it like this:
private void InvoiceItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Remove)
return;
foreach (var oldItem in e.OldItems)
{
//do any other processing necessary
}
}
That's because you will probably be having at least two ways of removing an item from you underlying collection (keyboard with Del key, some button) and maybe some other things to take care of when an item is deleted.
I bumped on that some time ago. Somehow this event is never raised.
Try
this approach.
Long story short, event PreviewKeyDown will get you where you want.
And in MVVM-friendly manner:
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<i:InvokeCommandAction Command="{Binding DeleteItemCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Did you check this answer yet?
How to bind delete action (in WPF Datagrid) to a command or property in view model
You probably need to:
make sure CanUserDeleteRows="False"
make sure the key is actually bound to the specified command in your datacontext, like this:
<DataGrid.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DataContext.DeleteEntry, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"/>
</DataGrid.InputBindings>
Previously I fail at item #2 and wrote Command="{Binding DeleteEntry}", while in fact I should bind to DataContext.DeleteEntry by RelativeSource.
I had to solve the issue in a different way than the jl.'s answer, because it could not do any processing before (e.g. checking access) deletion happen as a Command could. While perhaps not as robust, it does exactly what you asked about. Keeping your original code unchanged, just hook the following SelectionChanged, or even better use attached property.
Because deleting an item first produces SelectionChanged with index -1 it is easy enough to reliably guess when deletion happen and set a flag. After the first invocation with -1 another one with nearest neighbor index happens, at this point if the flag was set, it is safe to focus the current cell:
private int LastItemCount = 0;
private bool ShouldFocusOnSelection = false;
private void FocusOnDeleteDG_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is DataGrid dg)
{
if (IsRemovalEvent(dg, e))
{
ShouldFocusOnSelection = true;
}
else if (ShouldFocusOnSelection)
{
dg.FocusCurrentCell();
ShouldFocusOnSelection = false;
}
LastItemCount = dg.Items.Count;
}
}
where IsRemovalEvent checks if the selection event was produced by item removal:
private static bool IsRemovalEvent(DataGrid dg, SelectionChangedEventArgs e)
{
return e.RemovedItems.Count > 0
&& e.AddedItems.Count == 0
&& dg.SelectedIndex == -1
&& dg.Items.Count > 0
&& LastItemCount > dg.Items.Count;
}
and the FocusCurrentCell/GetChildren are helper methods you probably already have:
public static void FocusCurrentCell(this DataGrid dataGrid)
{
var rowIndex = dataGrid.SelectedIndex != -1 ? dataGrid.SelectedIndex : (dataGrid.Items.Count > 0 ? 0 : -1);
if (!(dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) is DataGridRow row))
{
return;
}
if (dataGrid.CurrentColumn?.DisplayIndex != null)
{
// traverse VisualTree using VisualTreeHelper.GetChild()
var cell = row.GetChildren<DataGridCell>()
.Skip(dataGrid.CurrentColumn.DisplayIndex).FirstOrDefault();
Keyboard.Focus(cell);
}
}

How to bind multiple selection of listview to viewmodel?

I am implementing a listview, and a button next to it. I have to be able that when i select multiple items in a listview, and then click on a button, then the selected items are put into a list. But my question is , how do i bind the selected items towards the viewmodel?
I changed my selectionmode to multiple. But then, do i just have to do:
SelectedItem={Binding path= selectedItems}
and then make in my viewmodel a property selectedItems, and it will set these items i have selected? Or what is the right solution to do this?
Like Doctor has already pointed out, you can bind SelectedItems to XAML CommandParameter
After a lot of digging and googling, I have finally found a simple solution to this common issue.
To make it work you must follow ALL the following rules:
Following Ed Ball's suggestion', on you XAML command databinding, define CommandParameter property BEFORE Command property. This a very time-consuming bug.
Make sure your ICommand's CanExecute and Execute methods have a parameter of object type. This way you can prevent silenced cast exceptions that occurs whenever databinding CommandParameter type does not match your command method's parameter type.
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)
{
// Your goes here
}
private bool OnDeleteSelectedItemsExecute(object SelectedItems)
{
// Your goes here
}
For example, you can either send a listview/listbox's SelectedItems property to you ICommand methods or the listview/listbox it self. Great, isn't it?
Hope it prevents someone spending the huge amount of time I did to figure out how to receive SelectedItems as CanExecute parameter.
It's kind of tricky to do this Mutliple Selection in MVVM, because the SelectedItems property isn't a Dependency Property. However, there are some tricks you can use. I found this triology of blog posts that describe the matter in some details and provide some useful solutions.
Part I
Part II
Part III
Hope this helps
If you are using System.Windows.Interactivity and Microsoft.Expression.Interactions already, here is a workaround without any other code/behaviour to mess around. If you need these, it can be download from here
This workaround make use of interactivity event trigger and interactions set property mechanism in above assemblies.
Additional namespace declaration in XAML
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
XAML:
<ListView Name="MyListView" ItemsSource="{Binding ModelList}" DisplayMemberPath="Name" Grid.Column="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<ei:ChangePropertyAction TargetObject="{Binding Mode=OneWay}" PropertyName="SelectedItems" Value="{Binding Path=SelectedItems, ElementName=MyListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
View Model:
public class ModelListViewModel
{
public ObservableCollection<Model> ModelList { get; set; }
public ObservableCollection<Model> SelectedModels { get; set; }
public ModelListViewModel() {
ModelList = new ObservableCollection<Model>();
SelectedModels = new ObservableCollection<Model>();
}
public System.Collections.IList SelectedItems {
get {
return SelectedModels;
}
set {
SelectedModels.Clear();
foreach (Model model in value) {
SelectedModels.Add(model);
}
}
}
}
In example above, your ViewModel will pick up the selected items whenever the selection on ListView changed.
What you can do is you can handle the Button_Click(...) in your code-behind. Then in that code-behind method you can create a List of selected items by iterating over the selected items of the listView.
Since it is allowed to access the ViewModel from the View you can now call a method on your ViewModel and pass the list of selected items as a parameter.
I'm not sure if this would also work with Bindings only, however it is not bad practice to use code-behind as well.
Example Code:
public void Button_Click(object sender, EventArguments arg)
{
List<ListViewItem> mySelectedItems = new List<ListViewItem>();
foreach(ListViewItem item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}
ViewModel.SomeMethod(mySelectedItems);
}
EDIT
Here is a minimalist example, XAML:
<DataTemplate
x:Key="CarTemplate"
DataType="{x:Type Car}">
</DataTemplate>
<ListView x:Name="myListView"
ItemsSource="{Binding Path=Cars}"
ItemTemplate="{StaticResource CarTemplate}">
</ListView>
CODE-BEHIND:
public void Button_Click(object sender, EventArguments arg)
{
List<Car> mySelectedItems = new List<Car>();
foreach(Car item in myListView.SelectedItems)
{
mySelectedItems.Add(item);
}
ViewModel.SomeMethod(mySelectedItems);
}
Unfortunately the SelectedItems is a read only not bindable property.
I found a lot of help from this article How to Databind to a SelectedItems property in WPF
If you are using Metro/WinRT you may want to look at the WinRTXXAMLToolkit as it offers a bindable SelectedItems dependency property as one of its extensions.
You can't bind, but you can send to Command as an CommandParameter.
As a slight variation on Christian's post, I implemented similar code using the ListView.SelectionChanged event. Instead of calling a method on the ViewModel, I set a property called SelectedItems:
public void ListView_SelectionChanged( object s, SelectionChangedEventArgs e ) {
List<Car> mySelectedItems = new List<Car>();
foreach( Car item in myListView.SelectedItems )
mySelectedItems.Add(item);
ViewModel.SelectedItems = mySelectedItems;
}
This way, ViewModel.SelectedItems is available for any command you might have in your ViewModel and it can be used for data binding (if you turn it into an ObservableCollection).
I did a solution for this, to me this was simple enough.
<ListBox ItemsSource="{Binding ListOfModel}" x:Name="ModelList"
SelectedItem="{Binding SelectedModel, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding ExecuteListBoxSelectionChange}" CommandParameter="{Binding ElementName=ModelList}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Then in the viewmodel:
public ICommand ExecuteListBoxSelectionChange { get; private set; }
ExecuteListBoxSelectionChange = DelegatingCommand<ListBox>.For(ListBoxSelectionChnageEvent).AlwaysEnabled();
SelectedModels is the list where I wanted the selection to be filled.
private void ListBoxSelectionChnageEvent(ListBox modelListBox)
{
List<ModelInfo> tempModelInfo = new List<ModelInfo>();
foreach(ModelInfo a in modelListBox.SelectedItems)
tempModelInfo.Add(a);
SelectedModels = tempModelInfo;
}

How do I detect row selection in the Xceed DataGrid for WPF

I'm horrible at this WPF thing, so bear with me.
I'm using the Xceed DataGrid for WPF, and I need to know when someone selects a row, but I can't figure out how to do it. I'm sure I need to add some XAML to enable this, but I can't figure out what I should do.
I use a MVVM approach and therefor favor data binding. I will bind the SelectedItem property to a SelectedItem property on my ViewModel object for the grid.
<xcdg:DataGridControl x:Name="grid" SelectedItem="{Binding SelectedItem}">
</xcdg:DataGridControl>
Then on your property setter can do what ever is necessary upon change in the SelectedItemChanged() method.
private IMyItem _selectedItem;
public IMyItem SelectedItem
{
get { return _selectedItem; }
set {
_selectedItem = value;
OnPropertyChanged("SelectedItem");
SelectedItemChanged();
}
}
I'm actually struggling a bit with the same thing myself, except I have a prerequisite that the selection notification be done via an ICommand; however, if you do not have this need, you can wire up the SelectionChanged event handler. It's pretty elementary stuff, but I'll include the code just in case:
XAML:
<Grid>
<DataGrid:DataGridControl x:Name="gridControl" SelectionChanged="gridControl_SelectionChanged">
<!-- Content -->
</DataGrid:DataGridControl>
</Grid>
Code-behind:
private void gridControl_SelectionChanged(object sender, Xceed.Wpf.DataGrid.DataGridSelectionChangedEventArgs e)
{
var selectedIndex = gridControl.SelectedIndex; // int index
var selectedItem = gridControl.SelectedItem; // instance of bound object
var selectedItems = gridControl.SelectedItems; // IList of bound objects
}
All that said, I'm very interested to hear if there are any elegant solutions for getting the selected row from an Xceed DataGrid with an ICommand (in my case, I'm using anonymous types, which can make a difference)...
You don't have to write complicated code for something simple... although it can become tedious, here is some code for you. I hope this helps:
<Style TargetType="xcdg:DataRow">
<EventSetter Handler="dr_PreviewMouseDown" Event="PreviewMouseDown" />
</Style>
void dr_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
DataRow dr = sender as DataRow;
Debug.WriteLine(sender);
}
So here's what I came up with
System.ComponentModel.DependencyPropertyDescriptor gridItemsSourceDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(DataGridControl.SelectedItemProperty, typeof(DataGridControl));
gridItemsSourceDescriptor.AddValueChanged(dgBaxRuns, HandleSelectionChanged);
I made for me a easiest way.
<xctk:MaterialButton Margin="5,0,5,0" Grid.Column="3" Content="Szűrt sorok kijelölése" Command="{Binding SelectFilteredRowsCommand}" CommandParameter="{Binding ElementName=MyDataGrid}" />
So, i send my datagrid with my commandparameter to the viewmodel.
public RelayCommand<object> SelectFilteredRowsCommand { get; set; }
SelectFilteredRowsCommand = new RelayCommand<object>((o) =>
{
var datagrid = o as DataGridControl;
if (datagrid != null)
{
var datagriditems = datagrid.Items.Cast<SelectableProduct>();
foreach (SelectableProduct selectableProduct in datagriditems)
{
selectableProduct.IsSelect = true;
}
}
});
And convert back to datagrid itemsoruce type.

Resources